Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 2.11.0-privacyPolicy
  • APIv2
  • Docs/Setup
  • Experiment/fix-debugging
  • Experimental/Heinrichs-cypress
  • Feature/xxxx-turnOffDataPub
  • Fix/xxxx-ToS400Error
  • Fix/xxxx-migrateLogin
  • Fix/xxxx-tokenUploadButton
  • Hotfix/0038-correctDownload
  • Hotfix/1917-PublicFilesVisibility
  • Hotfix/1963-fixOrganizationField
  • Hotfix/2015-PublicFilesVisibility
  • Hotfix/2130-uiv2ContactChange
  • Hotfix/2144-invitationCall
  • Hotfix/2150-fixUpload
  • Hotfix/2160-userOrgsInst
  • Hotfix/2190-requiredFieldsForUserProfile
  • Hotfix/2196-RCVTableTranslation
  • Hotfix/2212-fixFiles
  • Hotfix/2226-userProfileSaveButton
  • Hotfix/2232-dependencyFix
  • Hotfix/2233-fixMe
  • Hotfix/2258-saveButtonWorksAsExpected
  • Hotfix/2296-selectedValuesNotReturned
  • Hotfix/2308-defaultLicense
  • Hotfix/2335-fixingSearchRCV
  • Hotfix/2353-dropShape
  • Hotfix/2370-fixDeleteButton
  • Hotfix/2378-linkedFix
  • Hotfix/2379-filesDragAndDrop
  • Hotfix/2382-guestStillBuggy
  • Hotfix/2384-guestsAndLinked
  • Hotfix/2427-adminTrouble
  • Hotfix/2459-EncodingPath
  • Hotfix/2465-orcidLink
  • Hotfix/2465-orcidLink-v1.25.1
  • Hotfix/2504-formGen
  • Hotfix/2541-resCreate
  • Hotfix/2601-correctMetadataIdentity
  • Hotfix/2611-feedback
  • Hotfix/2618-turtle
  • Hotfix/2681-validationErrors
  • Hotfix/2684-correctEncoding
  • Hotfix/2684-fixSubMetadata
  • Hotfix/2713-validateEntryName
  • Hotfix/2734-allowEmptyLicense
  • Hotfix/2765-encodingAgain
  • Hotfix/2852-adaptTextForToSUi
  • Hotfix/2853-optimizationV4
  • Hotfix/2943-reloadingResources
  • Hotfix/2943-searchHighlighting
  • Hotfix/2957-styleAndUpgrade
  • Hotfix/2971-fixTextInDataPub
  • Hotfix/2989-cookieLength
  • Hotfix/662-keepSidebarExpanded
  • Hotfix/xxxx-correctLinking
  • Hotfix/xxxx-folderRecursive
  • Hotfix/xxxx-fullscreenCss
  • Hotfix/xxxx-homepageDisplay
  • Hotfix/xxxx-liveReleaseFixes
  • Hotfix/xxxx-partnerProjects
  • Hotfix/xxxx-workingFileIndex
  • Issue/1560-VisibilityAndOrder
  • Issue/1782-structualDataIntegration
  • Issue/1792-newMetadataStructure
  • Issue/1822-coscineUIv2App
  • Issue/1824-componentsUIv2
  • Issue/1824-routerAdditions
  • Issue/1825-codeQualityPipelines
  • Issue/1833-newLogin
  • Issue/1843-multipleFilesValidation
  • Issue/1860-searchScoping
  • Issue/1861-searchMetadata
  • Issue/1862-searchFacets
  • Issue/1863-paginationForSearch
  • Issue/1926-userProfile
  • Issue/1927-projectAppMigration
  • Issue/1928-sidebarmenuAddition
  • Issue/1929-vuexToPinia
  • Issue/1938-internalHandling
  • Issue/1951-quotaImplementation
  • Issue/1953-owlImports
  • Issue/1957-resourceAppMigration
  • Issue/1957-resourceAppMigrationNew
  • Issue/1962-SearchAppUI2
  • Issue/1964-tokenExpiryUIv2
  • Issue/1965-userListMigration
  • Issue/1970-breadcrumbs
  • Issue/1971-projectEditCreateMigration
  • Issue/1972-homeDepot
  • Issue/1974-shibbolethLogout
  • Issue/1976-resouceCreationVaildEmail
  • Issue/1979-supportAdminUIv2Migration
  • Issue/1980-userManagement
  • Issue/1985-adaptSidebar
  • Issue/2002-migrateResourceCreate
  • Issue/2003-resourceSettings
  • Issue/2008-quotaManagement
  • Issue/2011-pathConfig
  • 1.28.0-pilot
  • v1.0.0
  • v1.1.0
  • v1.10.0
  • v1.10.1
  • v1.10.2
  • v1.10.3
  • v1.11.0
  • v1.11.1
  • v1.11.2
  • v1.11.3
  • v1.11.4
  • v1.11.5
  • v1.11.6
  • v1.11.7
  • v1.12.0
  • v1.13.0
  • v1.14.0
  • v1.14.1
  • v1.14.2
  • v1.14.3
  • v1.15.0
  • v1.15.1
  • v1.16.0
  • v1.16.1
  • v1.16.2
  • v1.16.3
  • v1.17.0
  • v1.17.1
  • v1.17.2
  • v1.18.0
  • v1.18.1
  • v1.19.0
  • v1.2.0
  • v1.20.0
  • v1.20.1
  • v1.20.2
  • v1.20.3
  • v1.20.4
  • v1.20.5
  • v1.21.0
  • v1.22.0
  • v1.22.1
  • v1.22.2
  • v1.23.0
  • v1.23.1
  • v1.23.2
  • v1.23.3
  • v1.23.4
  • v1.23.5
  • v1.23.6
  • v1.23.6-patch-2417-2427
  • v1.24.0
  • v1.24.1
  • v1.25.0
  • v1.25.1
  • v1.26.0
  • v1.26.1
  • v1.27.0
  • v1.27.1
  • v1.27.1-pilot
  • v1.28.0
  • v1.29.0
  • v1.29.1
  • v1.29.2
  • v1.3.0
  • v1.30.0
  • v1.30.1
  • v1.30.2
  • v1.31.0
  • v1.32.0
  • v1.4.0
  • v1.4.1
  • v1.5.0
  • v1.6.0
  • v1.6.1
  • v1.6.2
  • v1.7.0
  • v1.8.0
  • v1.8.1
  • v1.8.2
  • v1.9.0
  • v2.0.0
  • v2.1.0
  • v2.10.0
  • v2.10.1
  • v2.11.0
  • v2.12.0
  • v2.12.1
  • v2.12.2
  • v2.12.3
  • v2.12.4
  • v2.12.5
  • v2.13.0
  • v2.13.1
  • v2.13.2
  • v2.13.3
  • v2.13.4
  • v2.14.0
  • v2.15.0
200 results

Target

Select target project
  • coscine/frontend/apps/ui
1 result
Select Git revision
  • 2.11.0-privacyPolicy
  • APIv2
  • Docs/Setup
  • Experiment/fix-debugging
  • Experimental/Heinrichs-cypress
  • Feature/xxxx-turnOffDataPub
  • Fix/xxxx-ToS400Error
  • Fix/xxxx-migrateLogin
  • Fix/xxxx-tokenUploadButton
  • Hotfix/0038-correctDownload
  • Hotfix/1917-PublicFilesVisibility
  • Hotfix/1963-fixOrganizationField
  • Hotfix/2015-PublicFilesVisibility
  • Hotfix/2130-uiv2ContactChange
  • Hotfix/2144-invitationCall
  • Hotfix/2150-fixUpload
  • Hotfix/2160-userOrgsInst
  • Hotfix/2190-requiredFieldsForUserProfile
  • Hotfix/2196-RCVTableTranslation
  • Hotfix/2212-fixFiles
  • Hotfix/2226-userProfileSaveButton
  • Hotfix/2232-dependencyFix
  • Hotfix/2233-fixMe
  • Hotfix/2258-saveButtonWorksAsExpected
  • Hotfix/2296-selectedValuesNotReturned
  • Hotfix/2308-defaultLicense
  • Hotfix/2335-fixingSearchRCV
  • Hotfix/2353-dropShape
  • Hotfix/2370-fixDeleteButton
  • Hotfix/2378-linkedFix
  • Hotfix/2379-filesDragAndDrop
  • Hotfix/2382-guestStillBuggy
  • Hotfix/2384-guestsAndLinked
  • Hotfix/2427-adminTrouble
  • Hotfix/2459-EncodingPath
  • Hotfix/2465-orcidLink
  • Hotfix/2465-orcidLink-v1.25.1
  • Hotfix/2504-formGen
  • Hotfix/2541-resCreate
  • Hotfix/2601-correctMetadataIdentity
  • Hotfix/2611-feedback
  • Hotfix/2618-turtle
  • Hotfix/2681-validationErrors
  • Hotfix/2684-correctEncoding
  • Hotfix/2684-fixSubMetadata
  • Hotfix/2713-validateEntryName
  • Hotfix/2734-allowEmptyLicense
  • Hotfix/2765-encodingAgain
  • Hotfix/2852-adaptTextForToSUi
  • Hotfix/2853-optimizationV4
  • Hotfix/2943-reloadingResources
  • Hotfix/2943-searchHighlighting
  • Hotfix/2957-styleAndUpgrade
  • Hotfix/2971-fixTextInDataPub
  • Hotfix/2989-cookieLength
  • Hotfix/662-keepSidebarExpanded
  • Hotfix/xxxx-correctLinking
  • Hotfix/xxxx-folderRecursive
  • Hotfix/xxxx-fullscreenCss
  • Hotfix/xxxx-homepageDisplay
  • Hotfix/xxxx-liveReleaseFixes
  • Hotfix/xxxx-partnerProjects
  • Hotfix/xxxx-workingFileIndex
  • Issue/1560-VisibilityAndOrder
  • Issue/1782-structualDataIntegration
  • Issue/1792-newMetadataStructure
  • Issue/1822-coscineUIv2App
  • Issue/1824-componentsUIv2
  • Issue/1824-routerAdditions
  • Issue/1825-codeQualityPipelines
  • Issue/1833-newLogin
  • Issue/1843-multipleFilesValidation
  • Issue/1860-searchScoping
  • Issue/1861-searchMetadata
  • Issue/1862-searchFacets
  • Issue/1863-paginationForSearch
  • Issue/1926-userProfile
  • Issue/1927-projectAppMigration
  • Issue/1928-sidebarmenuAddition
  • Issue/1929-vuexToPinia
  • Issue/1938-internalHandling
  • Issue/1951-quotaImplementation
  • Issue/1953-owlImports
  • Issue/1957-resourceAppMigration
  • Issue/1957-resourceAppMigrationNew
  • Issue/1962-SearchAppUI2
  • Issue/1964-tokenExpiryUIv2
  • Issue/1965-userListMigration
  • Issue/1970-breadcrumbs
  • Issue/1971-projectEditCreateMigration
  • Issue/1972-homeDepot
  • Issue/1974-shibbolethLogout
  • Issue/1976-resouceCreationVaildEmail
  • Issue/1979-supportAdminUIv2Migration
  • Issue/1980-userManagement
  • Issue/1985-adaptSidebar
  • Issue/2002-migrateResourceCreate
  • Issue/2003-resourceSettings
  • Issue/2008-quotaManagement
  • Issue/2011-pathConfig
  • 1.28.0-pilot
  • v1.0.0
  • v1.1.0
  • v1.10.0
  • v1.10.1
  • v1.10.2
  • v1.10.3
  • v1.11.0
  • v1.11.1
  • v1.11.2
  • v1.11.3
  • v1.11.4
  • v1.11.5
  • v1.11.6
  • v1.11.7
  • v1.12.0
  • v1.13.0
  • v1.14.0
  • v1.14.1
  • v1.14.2
  • v1.14.3
  • v1.15.0
  • v1.15.1
  • v1.16.0
  • v1.16.1
  • v1.16.2
  • v1.16.3
  • v1.17.0
  • v1.17.1
  • v1.17.2
  • v1.18.0
  • v1.18.1
  • v1.19.0
  • v1.2.0
  • v1.20.0
  • v1.20.1
  • v1.20.2
  • v1.20.3
  • v1.20.4
  • v1.20.5
  • v1.21.0
  • v1.22.0
  • v1.22.1
  • v1.22.2
  • v1.23.0
  • v1.23.1
  • v1.23.2
  • v1.23.3
  • v1.23.4
  • v1.23.5
  • v1.23.6
  • v1.23.6-patch-2417-2427
  • v1.24.0
  • v1.24.1
  • v1.25.0
  • v1.25.1
  • v1.26.0
  • v1.26.1
  • v1.27.0
  • v1.27.1
  • v1.27.1-pilot
  • v1.28.0
  • v1.29.0
  • v1.29.1
  • v1.29.2
  • v1.3.0
  • v1.30.0
  • v1.30.1
  • v1.30.2
  • v1.31.0
  • v1.32.0
  • v1.4.0
  • v1.4.1
  • v1.5.0
  • v1.6.0
  • v1.6.1
  • v1.6.2
  • v1.7.0
  • v1.8.0
  • v1.8.1
  • v1.8.2
  • v1.9.0
  • v2.0.0
  • v2.1.0
  • v2.10.0
  • v2.10.1
  • v2.11.0
  • v2.12.0
  • v2.12.1
  • v2.12.2
  • v2.12.3
  • v2.12.4
  • v2.12.5
  • v2.13.0
  • v2.13.1
  • v2.13.2
  • v2.13.3
  • v2.13.4
  • v2.14.0
  • v2.15.0
200 results
Show changes
Showing
with 1144 additions and 59 deletions
......@@ -7,34 +7,53 @@ export default {
--------------------------------------------------------------------------------------
*/
page: {
// ListProjects.vue
listProjects: {
title: "Project | Projects",
addProject: "Add Project",
},
// CreateProject.vue
createProject: {
title: "Create Project Page",
description:
"This is the @:page.createProject.title for the Coscine UIv2 App",
},
// Members.vue
members: {
title: "Members Page",
description: "This is the @:page.members.title for the Coscine UIv2 App",
},
// ProjectPage.vue
project: {
title: "Project Page",
resource: "Resource | Resources",
subProject: "Sub-Project | Sub-Projects",
member: "Member | Members",
addResource: "Add Resource",
members: {
toProjectMembers: "Manage Members....",
modal: {
// Leave project modal
title: "Leave project {name}",
body: "You are about to leave project {name}. You will also loose access to the project and its resources. Would you like to leave?",
},
tooltip: "Leave the project",
tooltipDisabled:
"You are the last project owner, add another project owner to leave this project.",
},
},
// Quota.vue
quota: {
title: "Quota Page",
description: "This is the @:page.quota.title for the Coscine UIv2 App",
},
// Settíngs.vue
settings: {
title: "Settings Page",
description: "This is the @:page.settings.title for the Coscine UIv2 App",
......
<template>
<div>
<b-row align-h="between">
<!-- Resources -->
<div id="resources" class="container">
<CoscineHeadline :headline="$parent.$tc('page.project.resource', 0)" />
<b-col id="resources">
<CoscineHeadline :headline="$parent.$tc('page.project.resource', 2)" />
<div class="list">
<b-card-group deck>
......@@ -26,10 +27,16 @@
/>
</b-card-group>
</div>
</div>
</b-col>
<!-- Members -->
<b-col id="members">
<Members v-if="project" />
</b-col>
</b-row>
<!-- Sub-Projects -->
<div id="sub-projects" class="container">
<div id="sub-projects">
<CoscineHeadline :headline="$parent.$tc('page.project.subProject', 0)" />
<div class="list">
......@@ -56,6 +63,7 @@
<script lang="ts">
import { defineComponent } from "vue-demi";
import Members from "./components/Members.vue";
import { RawLocation } from "vue-router";
import type {
ProjectObject,
......@@ -84,6 +92,10 @@ export default defineComponent({
},
},
components: {
Members,
},
computed: {
project(): ProjectObject | null {
return this.projectStore.currentProject;
......@@ -170,3 +182,16 @@ export default defineComponent({
},
});
</script>
<style scoped>
#resources {
/* Forces Members list to collapse down to
a new row at after certain window width */
min-width: 22rem;
}
#members {
/* Prevents Members list to become wider */
max-width: 27rem;
}
</style>
<template>
<div>
<CoscineHeadline
:headline="$parent.$parent.$tc('page.project.member', 2)"
/>
<!-- Members -->
<div class="list">
<div class="user-complete-name-list container-fluid">
<span v-for="(projectRole, index) in projectRoles" :key="index">
<b-row align-v="center" class="mb-1">
<!-- Icon -->
<b-icon icon="person" variant="primary" class="user-image" />
<!-- Member Details -->
<b-col class="user-details">
{{ projectRole.user.displayName }}
<br />
<a
v-b-tooltip.hover.bottom="projectRole.user.emailAddress"
:href="'mailto:' + projectRole.user.emailAddress"
>
{{ projectRole.user.emailAddress }}
</a>
</b-col>
<!-- Leave Button -->
<b-button
id="leave"
class="float-right"
variant="primary"
name="leave"
size="sm"
v-if="projectRole.user.id === currentUser.id"
v-on:click="leaveModal = true"
:disabled="ownerCount < 2 && isOwner"
tabindex="0"
v-b-tooltip.hover.bottom
:title="
ownerCount < 2 && isOwner
? $parent.$parent.$t('page.project.members.tooltipDisabled')
: $parent.$parent.$t('page.project.members.tooltip')
"
>{{ $t("buttons.leave") }}</b-button
>
</b-row>
</span>
</div>
<!-- Manage Members Link -->
<div v-if="isOwner" class="mt-2">
<router-link :to="{ name: 'project-members' }">
{{ $parent.$parent.$t("page.project.members.toProjectMembers") }}
</router-link>
</div>
<!-- Leave Modal -->
<b-modal
id="leaveModal"
v-model="leaveModal"
hide-footer
:title="
$parent.$parent.$t('page.project.members.modal.title', {
name: project.displayName,
})
"
>
<!-- Leave Modal Body -->
<div class="mb-3">
{{
$parent.$parent.$t("page.project.members.modal.body", {
name: project.displayName,
})
}}
</div>
<!-- Leave Modal Buttons -->
<b-button
class="float-right"
variant="primary"
v-on:click="leaveModal = false"
ref="modalLeaveProjectCancel"
autofocus
>{{ $parent.$parent.$t("buttons.cancel") }}</b-button
>
<b-button
class="float-Left"
variant="danger"
v-on:click="leaveProject()"
>{{ $parent.$parent.$t("buttons.leave") }}</b-button
>
</b-modal>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue-demi";
import type {
ProjectObject,
ProjectRoleObject,
UserObject,
} from "@coscine/api-client/dist/types/Coscine.Api.Project";
// import the store for current module
import { useProjectStore } from "../../store";
// import the main store
import { useMainStore } from "@/store/index";
import { useUserStore } from "@/modules/user/store";
export default defineComponent({
setup() {
const mainStore = useMainStore();
const projectStore = useProjectStore();
const userStore = useUserStore();
return { mainStore, projectStore, userStore };
},
data() {
return {
ownerString: "Owner",
leaveModal: false,
};
},
computed: {
currentUser(): UserObject | null {
return this.userStore.user;
},
project(): ProjectObject | null {
return this.projectStore.currentProject;
},
projectRoles(): ProjectRoleObject[] | null {
return this.projectStore.currentProjectRolesSorted;
},
isOwner(): boolean | undefined {
const userRole = this.projectStore.currentUserRole;
if (userRole && userRole.displayName === this.ownerString) {
return true;
} else {
return false;
}
},
ownerCount(): number | undefined {
const currentRoles = this.projectStore.currentProjectRoles;
if (currentRoles) {
return currentRoles.filter(
(projectRole) =>
projectRole.role &&
projectRole.role.displayName === this.ownerString
).length;
}
return undefined;
},
},
methods: {
leaveProject() {
this.projectStore.leaveProject(this.project);
},
},
});
</script>
<style scoped>
.user-image {
width: 2rem;
height: 2rem;
}
.user-details {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user-complete-name-list {
max-height: 25rem;
overflow-x: hidden;
overflow-y: auto;
}
</style>
......@@ -36,20 +36,33 @@ export const ProjectRoutes: RouteConfig[] = [
component: ListProjects,
},
],
meta: {
breadCrumb: "home",
},
},
{
path: "/p/:slug",
component: ProjectModule,
meta: {
breadCrumb: "project.home",
default: "project-home",
},
children: [
{
path: "/",
name: "project-home",
component: ProjectPage,
meta: {
breadCrumb: "project.home",
},
},
{
path: "create-project",
name: "create-sub-project",
component: CreateProject,
meta: {
breadCrumb: "project.create",
},
},
{
path: "settings",
......@@ -57,6 +70,7 @@ export const ProjectRoutes: RouteConfig[] = [
component: Settings,
meta: {
requiresAuth: true,
breadCrumb: "project.settings",
},
},
{
......@@ -65,6 +79,7 @@ export const ProjectRoutes: RouteConfig[] = [
component: Quota,
meta: {
requiresAuth: true,
breadCrumb: "project.quota",
},
},
{
......@@ -73,6 +88,7 @@ export const ProjectRoutes: RouteConfig[] = [
component: Members,
meta: {
requiresAuth: true,
breadCrumb: "project.members",
},
},
...ResourceRoutes,
......
......@@ -4,13 +4,17 @@ import { StatusCodes } from "http-status-codes";
import { reactive } from "vue-demi";
import { removeQueryParameterFromUrl } from "@/router";
import { ProjectApi, SubProjectApi } from "@coscine/api-client";
import { ProjectApi, ProjectRoleApi, SubProjectApi } from "@coscine/api-client";
import type {
ProjectObject,
ProjectRoleObject,
ResourceObject,
RoleObject,
} from "@coscine/api-client/dist/types/Coscine.Api.Project";
import type { Route } from "vue-router";
import useUserStore from "../user/store";
/*
Store variable name is "this.<id>Store"
id: "project" --> this.projectStore
......@@ -24,6 +28,7 @@ export const useProjectStore = defineStore({
--------------------------------------------------------------------------------------
*/
state: (): ProjectState => ({
allProjects: null,
currentSlug: null,
visitedProjects: {},
topLevelProjects: null,
......@@ -60,6 +65,84 @@ export const useProjectStore = defineStore({
return null;
}
},
currentParentProjects(): ProjectObject[] | null {
if (
this.currentSlug &&
this.visitedProjects[this.currentSlug] &&
this.allProjects
) {
const parentProjects = [];
let currentParentProjectId =
this.visitedProjects[this.currentSlug].parentId;
while (
currentParentProjectId &&
currentParentProjectId !== "00000000-0000-0000-0000-000000000000"
) {
const parentProject = this.allProjects.find(
(project) => project.id === currentParentProjectId
);
if (parentProject) {
if (parentProject in parentProjects) {
break;
}
parentProjects.push(parentProject);
currentParentProjectId = parentProject.parentId;
} else {
break;
}
}
return parentProjects;
} else {
return null;
}
},
currentProjectRoles(): ProjectRoleObject[] | null {
if (this.currentSlug && this.visitedProjects[this.currentSlug]) {
return this.visitedProjects[this.currentSlug].roles;
} else {
return null;
}
},
currentProjectRolesSorted(): ProjectRoleObject[] | null {
const sorted = this.currentProjectRoles;
if (sorted) {
sorted.sort((a, b) => {
if (
a.user !== null &&
a.user !== undefined &&
b.user !== null &&
b.user !== undefined &&
a.user.displayName !== null &&
a.user.displayName !== undefined &&
b.user.displayName !== null &&
b.user.displayName !== undefined
) {
const valueA = a.user.displayName.toUpperCase();
const valueB = b.user.displayName.toUpperCase();
return valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
} else {
return 0;
}
});
return sorted;
} else {
return null;
}
},
currentUserRole(): RoleObject | undefined | null {
const currentRoles = this.currentProjectRoles;
const userStore = useUserStore();
const currentUser = userStore.user;
if (currentRoles && currentUser) {
const userProjectRole = currentRoles.find(
(projectRole) =>
projectRole.user && projectRole.user.id === currentUser.id
);
return userProjectRole ? userProjectRole.role : null;
} else {
return null;
}
},
},
/*
--------------------------------------------------------------------------------------
......@@ -72,6 +155,15 @@ export const useProjectStore = defineStore({
@click = "this.projectStore.<action_name>();
*/
actions: {
async retrieveAllProjects() {
const apiResponse = await ProjectApi.projectIndex();
if (apiResponse.status === StatusCodes.OK) {
this.allProjects = apiResponse.data;
} else {
// Handle other Status Codes
}
},
async retrieveTopLevelProjects() {
const apiResponse = await ProjectApi.projectGetTopLevelProjects();
if (apiResponse.status === StatusCodes.OK) {
......@@ -164,11 +256,41 @@ export const useProjectStore = defineStore({
...project,
resources: null,
subProjects: null,
roles: null,
});
this.visitedProjects[project.slug] = visitedProject;
}
}
},
async retrieveProjectRoles(project: ProjectObject | null) {
if (project && project.slug && project.id) {
const apiResponse = await ProjectRoleApi.projectRoleIndex(project.id);
if (apiResponse.status === StatusCodes.OK) {
this.visitedProjects[project.slug].roles = apiResponse.data;
} else {
// Handle other Status Codes
}
} else {
console.error("Selected project is null or its ID is undefined.");
}
},
async leaveProject(project: ProjectObject | null) {
if (project && project.slug && project.id) {
const apiResponse = await ProjectRoleApi.projectRoleDelete2(project.id);
if (apiResponse.status === StatusCodes.OK) {
this.visitedProjects[project.slug].roles = null;
// reset the project store to fetch the data again
this.$reset();
this.router.push({ name: "list-projects" });
} else {
// Handle other Status Codes
}
} else {
console.error("Selected project is null or its ID is undefined.");
}
},
},
});
......
import type {
ProjectObject,
ProjectRoleObject,
ResourceObject,
} from "@coscine/api-client/dist/types/Coscine.Api.Project";
export interface VisitedProjectObject extends ProjectObject {
resources: ResourceObject[] | null;
subProjects: ProjectObject[] | null;
roles: ProjectRoleObject[] | null;
}
export interface ProjectState {
......@@ -14,6 +16,7 @@ export interface ProjectState {
STATE TYPE DEFINITION
--------------------------------------------------------------------------------------
*/
allProjects: ProjectObject[] | null;
currentSlug: string | null;
visitedProjects: { [slug: string]: VisitedProjectObject };
topLevelProjects: ProjectObject[] | null;
......
......@@ -16,6 +16,7 @@ export const ResourceRoutes: RouteConfig[] = [
name: "create-resource",
component: CreateResource,
meta: {
breadCrumb: "resource.create",
requiresAuth: true,
},
},
......@@ -24,17 +25,25 @@ export const ResourceRoutes: RouteConfig[] = [
{
path: "r/:guid",
component: ResourceModule,
meta: {
breadCrumb: "resource.home",
default: "resource-home",
},
children: [
{
path: "/",
name: "resource-home",
component: ResourcePage,
meta: {
breadCrumb: "resource.home",
},
},
{
path: "settings",
name: "resource-settings",
component: Settings,
meta: {
breadCrumb: "resource.settings",
requiresAuth: true,
},
},
......
......@@ -36,9 +36,8 @@ export default defineComponent({
async initialize() {
// do initialization stuff (e.g. API calls, element loading, etc.)
// ...
this.searchStore.retrieveSearchResults();
},
},
});
</script>
<style></style>
import { DummyDataEntry, DummyDataType } from "../types";
export const mainViewData: Array<DummyDataType> = [
{
header: [
{
type: "Project",
value: "SomeExampleProjectName1",
},
],
body: [
{
type: "Created By",
value: "Max Mustermann",
},
{
type: "Parent Project",
value: "None",
},
{
type: "Start Date",
value: "01.01.2020",
},
{
type: "End Date",
value: "01.12.2022",
},
{
type: "PI",
value: "Prof. Müller",
},
{
type: "Grant Id",
value: "DFG 1866",
},
],
},
{
header: [
{
type: "Project",
value: "SomeExampleProjectName2",
},
],
body: [
{
type: "Created By",
value: "Manuel Rosenberger",
},
{
type: "Parent Project",
value: "None",
},
{
type: "Start Date",
value: "17.07.2020",
},
{
type: "End Date",
value: "-",
},
{
type: "PI",
value: "Prof. Müller",
},
{
type: "Grant Id",
value: "DFG 7753",
},
],
},
{
header: [
{
type: "Project",
value: "SomeExampleProjectName3",
},
],
body: [
{
type: "Created By",
value: "Hildegard Beck",
},
{
type: "Parent Project",
value: "SomeExampleProjectName1",
},
{
type: "Start Date",
value: "01.01.2020",
},
{
type: "End Date",
value: "01.12.2022",
},
{
type: "PI",
value: "Prof. Müller",
},
{
type: "Grant Id",
value: "DFG 1867",
},
],
},
{
header: [
{
type: "Resource",
value: "SomeExampleResourceName",
archived: true,
} as DummyDataEntry,
],
body: [
{
type: "Persistent ID",
value: "21.11102/0372a8a6-3f4f-448e-aa75-acb75-acb148fe66d6",
},
{
type: "License",
value: "MIT",
},
{
type: "Usage Rights",
value: "None",
},
{
type: "Application Profile",
value: "Radar",
},
],
},
{
header: [
{
type: "Resource",
value: "Test-Resource",
},
],
body: [
{
type: "Persistent ID",
value: "21.11102/cf39b3e6-e0b7-4415-bc65-acb75-fbbbc35b4b89",
},
{
type: "License",
value: "MIT",
},
{
type: "Usage Rights",
value: "None",
},
{
type: "Application Profile",
value: "Engmeta",
},
],
},
{
header: [
{
type: "Resource",
value: "Epic Project Resource",
archived: true,
} as DummyDataEntry,
],
body: [
{
type: "Persistent ID",
value: "21.11102/2a108fee-d60a-40e8-809a-acb75-8cf5aecb93f9",
},
{
type: "License",
value: "MIT",
},
{
type: "Usage Rights",
value: "None",
},
{
type: "Application Profile",
value: "Radar",
},
],
},
{
header: [
{
type: "Resource",
value: "Epic Project Resource 2",
},
],
body: [
{
type: "Persistent ID",
value: "21.11102/3f1bb053-644b-42c0-8e45-acb75-40193cde1a14",
},
{
type: "License",
value: "MIT",
},
{
type: "Usage Rights",
value: "None",
},
{
type: "Application Profile",
value: "Radar",
},
],
},
{
header: [
{
type: "File",
value: "LabNotes.txt",
},
],
body: [
{
type: "MD1",
value: "xxxxxxx",
},
{
type: "MD2",
value: "yyyyyyyy",
},
{
type: "MD3",
value: "None",
},
{
type: "MD4",
value: "wwwwwww",
},
{
type: "MD5",
value: "yyyyyyyyyyy",
},
{
type: "MD6",
value: "None",
},
{
type: "MD7",
value: "wwwwww",
},
{
type: "MD8",
value: "yyyyyyyyyyyyy",
},
{
type: "MD9",
value: "None",
},
{
type: "MD10",
value: "wwwwwwww",
},
{
type: "MD11",
value: "yyyyyyyyyyy",
},
{
type: "MD12",
value: "None",
},
{
type: "MD13",
value: "wwwwww",
},
{
type: "MD14",
value: "yyyyyyyyy",
},
{
type: "MD15",
value: "None",
},
{
type: "MD16",
value: "wwwwwwww",
},
],
},
{
header: [
{
type: "User",
value: "Max Mustermann",
},
],
body: [
{
type: "Project Member",
value:
"SomeExampleProjectName1, SomeExampleProjectName2, SomeExampleProjectName3",
},
],
},
{
header: [
{
type: "User",
value: "Manuel Rosenberger",
},
],
body: [
{
type: "Project Member",
value: "SomeExampleProjectName2",
},
],
},
{
header: [
{
type: "User",
value: "Hildegard Beck",
},
],
body: [
{
type: "Project Member",
value: "SomeExampleProjectName1, SomeExampleProjectName3",
},
],
},
];
......@@ -9,7 +9,18 @@ export default {
page: {
search: {
title: "Suchseite",
description: "Das ist die @:page.search.title des Coscine UIv2 Apps",
search: "Suchen",
buttonSearch: {
Item1: "Eintrag 1",
Item2: "Eintrag 2",
},
emptySearch: "Es wurden keine Treffer gefunden",
endSearchResults: "Alle Ergebnisse werden angezeigt",
allProjects: "Alle Projekte",
allResources: "Alle Resourcen",
},
},
} as VueI18n.LocaleMessageObject;
......@@ -9,7 +9,18 @@ export default {
page: {
search: {
title: "Search Page",
description: "This is the @:page.search.title for the Coscine UIv2 App",
search: "Search",
buttonSearch: {
Item1: "Item1",
Item2: "Item2",
},
emptySearch: "No item is found for the given search criteria",
endSearchResults: "Showing all results",
allProjects: "All Projects",
allResources: "All Resources",
},
},
} as VueI18n.LocaleMessageObject;
<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 class="search">
<CoscineHeadline :headline="$parent.$t('page.search.title')" />
<b-row id="mainRow">
<!-- Sidebar -->
<Sidebar />
<b-col ref="rightCol" sm="10" align-self="end" style="height: 100%">
<!-- Search Bar Fields -->
<b-row id="searchBarContainer" align-content="center">
<b-col id="searchField" align-self="start" class="pl-0">
<b-form-input
v-model="searchText"
:placeholder="$parent.$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") }}
</b-form-select-option>
</template>
</b-form-select>
</b-col>
<b-col sm="2" id="selectResCol" align-self="center" class="pl-0">
<b-form-select v-model="selectResValue">
<template #first>
<b-form-select-option :value="null" disabled
>{{ $parent.$t("page.search.allResources") }}
</b-form-select-option>
</template>
</b-form-select>
</b-col>
<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") }}
</b-button>
<b-dropdown id="searchDropdown" right size="sm" variant="primary">
<b-dropdown-item>{{
$parent.$t("page.search.buttonSearch.Item1")
}}</b-dropdown-item>
<b-dropdown-item>{{
$parent.$t("page.search.buttonSearch.Item2")
}}</b-dropdown-item>
</b-dropdown>
</b-button-group>
</b-col>
</b-row>
<!-- Filter Tags -->
<b-row
id="filterTagsContainer"
align-self="center"
class="mt-2 mb-2 rounded bg-light"
style="min-height: 37px"
>
<b-col align-self="center" class="pl-0">
<b-form-tags
v-model="filterTags"
no-outer-focus
class="border-0 bg-transparent"
>
<template v-slot="{ tags, removeTag }">
<div>
<CoscineHeadline :headline="$parent.$t('page.search.title')" />
<p class="mb-8 leading-relaxed dark:text-white">
{{ $parent.$t("page.search.description") }}
</p>
<img alt="From Coscine Old" src="@/assets/images/Search.png" />
<b-form-tag
v-for="tag in tags"
@remove="removeTag(tag)"
:key="tag"
:title="tag"
variant="primary"
pill
class="mr-1"
>{{ tag }}
</b-form-tag>
</div>
</template>
</b-form-tags>
</b-col>
<b-col align-self="center" sm="1" class="text-right">
<b-button id="filterTagsButton" size="sm" variant="light">
<b-icon icon="funnel-fill" />
</b-button>
</b-col>
</b-row>
<!-- Results View -->
<b-row id="resultsViewContainer" class="flex-grow-1">
<b-card style="width: 100%">
<b-skeleton-wrapper :loading="resultsViewLoading">
<template #loading>
<div
v-for="(entry, index) in 3"
:key="index"
class="p-2 border-top"
style="height: 105px"
>
<b-skeleton width="30%" class="m-2 mb-3"></b-skeleton>
<b-skeleton width="95%" class="m-2"></b-skeleton>
<b-skeleton width="40%" class="m-2"></b-skeleton>
</div>
</template>
<b-table
id="resultsView"
:items="resultsViewData"
:fields="resultsViewFields"
:per-page="paginationPerPage"
:current-page="paginationCurrentPage"
thead-class="d-none"
style="min-height: 100%"
small
hover
sticky-header
show-empty
>
<template #cell(header)="data">
<Result :result="data.item" />
</template>
<template #empty>
<h6 class="text-center">
{{ $parent.$t("page.search.emptySearch") }}
</h6>
</template>
<template #custom-foot="foot">
<!-- Show footer if there are results and only on the last page -->
<div
v-if="
foot.items.length > 0 &&
paginationCurrentPage ===
Math.ceil(paginationTotalRows / paginationPerPage)
"
class="p-2 text-center text-muted border-top"
>
{{ $parent.$t("page.search.endSearchResults") }}
</div>
</section>
</template>
</b-table>
</b-skeleton-wrapper>
</b-card>
</b-row>
</b-col>
</b-row>
<!-- Pagination -->
<b-row class="mt-1 mb-1 text-right" align-v="center">
<b-col align-self="center" class="p-0" />
<b-col align-self="center" class="p-0">
<b-pagination
id="pagination"
v-model="paginationCurrentPage"
:total-rows="paginationTotalRows"
:per-page="paginationPerPage"
aria-controls="resultsView"
align="center"
></b-pagination>
</b-col>
<b-col align-self="center" class="p-0">
<b-form-select
v-model="paginationPerPage"
:options="paginationPerPageOptions"
style="max-width: 5rem"
></b-form-select>
</b-col>
</b-row>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue-demi";
import CoscineHeadline from "@/components/CoscineHeadline.vue";
import Result from "./components/Result.vue";
import Sidebar from "./components/Sidebar.vue";
// import the store for current module
import { useSearchStore } from "../store";
// import the main store
import { useMainStore } from "@/store/index";
import type { DummyDataType } from "../types";
export default defineComponent({
setup() {
const mainStore = useMainStore();
......@@ -31,8 +189,84 @@ export default defineComponent({
return { mainStore, searchStore };
},
data() {
return {
searchText: "",
selectProjValue: null,
selectResValue: null,
filterTags: ["Filter 1", "Filter 2", "Filter 3"],
resultsViewFields: ["header"],
resultsViewLoading: true,
paginationCurrentPage: 1,
paginationPerPage: 10,
paginationPerPageOptions: [5, 10, 20, 50, 100],
paginationTotalRows: 0,
};
},
components: {
CoscineHeadline,
Result,
Sidebar,
},
computed: {
resultsViewData(): DummyDataType[] | null {
return this.searchStore.searchResults;
},
},
watch: {
resultsViewData() {
if (this.resultsViewData) {
this.paginationTotalRows = this.resultsViewData.length;
}
},
},
created() {
this.showResults();
},
methods: {
/* --- get search query to show results ---
getSearchQuery() {
const urlSearchParams = new URLSearchParams(window.location.search);
const query = Object.fromEntries(urlSearchParams.entries());
return query !== null ? decodeURIComponent(query.q) : "";
},
*/
showResults() {
setTimeout(() => {
this.resultsViewLoading = false;
}, 1500);
},
},
});
</script>
<style scoped>
#mainRow {
/* this style stretches the page vertically to fit the screen:-moz-animation:
- container-fluid <-> top = 59px
- mainRow <-> container-fluid = 53px
- mainRow <-> bottom = 62px
*/
height: calc(100vh - 59px - 53px - 62px);
}
#pagination {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
#resultsViewContainer {
/* this style stretches the results table vertically
- resultsViewContainer <-> mainRow = 91px
*/
height: calc(100% - 91px);
}
.card-body {
padding: 0rem;
}
</style>
<template>
<div class="p-2">
<a href="#">
<div
id="resultHeader"
v-for="(header, index) in result.header"
:key="index"
class="mb-2 text-left text-primary"
>
<b>{{ header.type }}</b
>: {{ header.value }}
<b-badge v-if="header.archived" pill variant="warning" class="ml-1">{{
$t("default.archived")
}}</b-badge>
</div>
</a>
<div id="resultBody" class="text-left">
<span
v-for="(element, index) in result.body"
:key="index"
class="mr-3 d-inline-block"
>
<!-- Keep the <br/> element at the end to have
double mouse click text selection work properly and
not have a table horizontal scrollbar appear -->
<b>{{ element.type }}</b
>: {{ element.value }}<br />
</span>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from "vue-demi";
import { DummyDataType } from "../../types";
// import the store for current module
import { useSearchStore } from "../../store";
// import the main store
import { useMainStore } from "@/store/index";
export default defineComponent({
setup() {
const mainStore = useMainStore();
const searchStore = useSearchStore();
return { mainStore, searchStore };
},
data() {
return {};
},
props: {
result: {
required: true,
type: Object as PropType<DummyDataType>,
},
},
methods: {},
});
</script>
<template>
<b-col>
<b-card
id="sidebarContainer"
class="progress-bar-striped text-center bg-light"
style="height: 100%"
>
<!-- Sidebar components come here -->
</b-card>
</b-col>
</template>
<script lang="ts">
import { defineComponent } from "vue-demi";
// import the store for current module
import { useSearchStore } from "../../store";
// import the main store
import { useMainStore } from "@/store/index";
export default defineComponent({
setup() {
const mainStore = useMainStore();
const searchStore = useSearchStore();
return { mainStore, searchStore };
},
});
</script>
<style scoped>
.card-body {
padding: 0rem;
}
</style>
......@@ -7,6 +7,9 @@ export const SearchRoutes: RouteConfig[] = [
{
path: "/search",
component: SearchModule,
meta: {
breadCrumb: "search",
},
children: [
{
path: "/",
......
import { defineStore } from "pinia";
import { SearchState } from "./types";
import { mainViewData } from "./assets/dummy_data";
import type { DummyDataType } from "./types";
/*
Store variable name is "this.<id>Store"
......@@ -13,7 +16,9 @@ export const useSearchStore = defineStore({
STATES
--------------------------------------------------------------------------------------
*/
state: (): SearchState => ({}),
state: (): SearchState => ({
searchResults: null,
}),
/*
--------------------------------------------------------------------------------------
......@@ -24,7 +29,11 @@ export const useSearchStore = defineStore({
In a component use as e.g.:
:label = "this.searchStore.<getter_name>;
*/
getters: {},
getters: {
retrieveDummyResults(): DummyDataType[] {
return mainViewData;
},
},
/*
--------------------------------------------------------------------------------------
ACTIONS
......@@ -35,7 +44,12 @@ export const useSearchStore = defineStore({
In a component use as e.g.:
@click = "this.searchStore.<action_name>();
*/
actions: {},
actions: {
retrieveSearchResults() {
// Currently using only Dummy Data
this.searchResults = this.retrieveDummyResults;
},
},
});
export default useSearchStore;
import type { SearchResult } from "@coscine/api-client/dist/types/Coscine.Api.Search";
export interface SearchState {
/*
--------------------------------------------------------------------------------------
STATE TYPE DEFINITION
--------------------------------------------------------------------------------------
*/
searchResults: DummyDataType[] | null; // Fix type for real API use
}
/* DELETE AFTER DATA IS ACTUALLY FETCHED OVER THE API */
export type DummyDataType = {
header: Array<DummyDataEntry>;
body: Array<DummyDataEntry>;
};
/* DELETE AFTER DATA IS ACTUALLY FETCHED OVER THE API */
export type DummyDataEntry = {
type: string;
value: string;
};
......@@ -7,14 +7,15 @@ export const UserRoutes: RouteConfig[] = [
{
path: "/user",
component: UserModule,
meta: {
breadCrumb: "user.profile",
requiresAuth: true,
},
children: [
{
path: "/",
name: "userprofile",
component: UserProfile,
meta: {
requiresAuth: true,
},
},
],
},
......
......@@ -144,6 +144,10 @@ export const useUserStore = defineStore({
} catch {
console.error("Could not fetch user");
// TODO: Handle error
const mainStore = useMainStore();
mainStore.$patch((state) => {
state.coscine.authorization.bearer = "";
});
}
}
},
......@@ -163,12 +167,7 @@ export const useUserStore = defineStore({
await UserApi.userUpdateUser(user);
},
setUserLanguagePreference() {
if (
this.user !== null &&
this.user.language !== null &&
this.user.language?.abbreviation !== null &&
this.user.language?.abbreviation !== undefined
) {
if (this.user && this.user.language && this.user.language?.abbreviation) {
const mainStore = useMainStore();
mainStore.coscine.locale = this.user.language?.abbreviation;
}
......
......@@ -12,7 +12,7 @@ export const def = {
} as VueI18n.LocaleMessages;
const i18n = new VueI18n({
locale: "en",
locale: localStorage.getItem("coscine.locale")?.toString(),
fallbackLocale: "en",
messages: def,
silentFallbackWarn: true,
......