web/satellite: added ability to edit project name
WHAT: added edit project dropdown to navigation side bar. edit project details page implemented. added ability to edit project name. project details section removed from project dashboard. WHY: enable users to change their project name. Change-Id: I36b6214ffe7adf4a12a1a09530ff1212e926aafe
This commit is contained in:
parent
8182fdad0b
commit
2668ec818e
38
web/satellite/package-lock.json
generated
38
web/satellite/package-lock.json
generated
@ -2857,9 +2857,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "14.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.10.1.tgz",
|
||||
"integrity": "sha512-aYNbO+FZ/3KGeQCEkNhHFRIzBOUgc7QvcVNKXbfnhDkSfwUv91JsQQa10rDgKSTSLkXZ1UIyPe4FJJNVgw1xWQ=="
|
||||
"version": "14.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.10.2.tgz",
|
||||
"integrity": "sha512-IzMhbDYCpv26pC2wboJ4MMOa9GKtjplXfcAqrMeNJpUUwpM/2ATt2w1JPUXwS6spu856TvKZL2AOmeU2rAxskw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -6562,9 +6562,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.568",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.568.tgz",
|
||||
"integrity": "sha512-j9MlEwgTHVW/lq93Hw8yhzA886oLjDm3Hz7eDkWP2v4fzLVuqOWhpNluziSnmR/tBqgoYldagbLknrdg+B7Tlw==",
|
||||
"version": "1.3.570",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.570.tgz",
|
||||
"integrity": "sha512-Y6OCoVQgFQBP5py6A/06+yWxUZHDlNr/gNDGatjH8AZqXl8X0tE4LfjLJsXGz/JmWJz8a6K7bR1k+QzZ+k//fg==",
|
||||
"dev": true
|
||||
},
|
||||
"elliptic": {
|
||||
@ -9930,9 +9930,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"klona": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.3.tgz",
|
||||
"integrity": "sha512-CgPOT3ZadDpXxKcfV56lEQ9OQSZ42Mk26gnozI+uN/k39vzD8toUhRQoqsX0m9Q3eMPEfsLWmtyUpK/yqST4yg==",
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz",
|
||||
"integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==",
|
||||
"dev": true
|
||||
},
|
||||
"known-css-properties": {
|
||||
@ -10891,9 +10891,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node-forge": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz",
|
||||
"integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==",
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
||||
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
|
||||
"dev": true
|
||||
},
|
||||
"node-gyp": {
|
||||
@ -13617,12 +13617,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"selfsigned": {
|
||||
"version": "1.10.7",
|
||||
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz",
|
||||
"integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==",
|
||||
"version": "1.10.8",
|
||||
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz",
|
||||
"integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"node-forge": "0.9.0"
|
||||
"node-forge": "^0.10.0"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
@ -14615,9 +14615,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "14.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.10.1.tgz",
|
||||
"integrity": "sha512-aYNbO+FZ/3KGeQCEkNhHFRIzBOUgc7QvcVNKXbfnhDkSfwUv91JsQQa10rDgKSTSLkXZ1UIyPe4FJJNVgw1xWQ=="
|
||||
"version": "14.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.10.2.tgz",
|
||||
"integrity": "sha512-IzMhbDYCpv26pC2wboJ4MMOa9GKtjplXfcAqrMeNJpUUwpM/2ATt2w1JPUXwS6spu856TvKZL2AOmeU2rAxskw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import { BaseGql } from '@/api/baseGql';
|
||||
import { ErrorUnauthorized } from '@/api/errors/ErrorUnauthorized';
|
||||
import { CreateProjectFields, Project, ProjectLimits, ProjectsApi } from '@/types/projects';
|
||||
import { Project, ProjectFields, ProjectLimits, ProjectsApi } from '@/types/projects';
|
||||
import { HttpClient } from '@/utils/httpClient';
|
||||
|
||||
export class ProjectsApiGql extends BaseGql implements ProjectsApi {
|
||||
@ -13,10 +13,10 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi {
|
||||
/**
|
||||
* Creates project.
|
||||
*
|
||||
* @param createProjectFields - contains project information
|
||||
* @param projectFields - contains project information
|
||||
* @throws Error
|
||||
*/
|
||||
public async create(createProjectFields: CreateProjectFields): Promise<Project> {
|
||||
public async create(projectFields: ProjectFields): Promise<Project> {
|
||||
const query =
|
||||
`mutation($name: String!, $description: String!) {
|
||||
createProject(
|
||||
@ -28,13 +28,13 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi {
|
||||
}`;
|
||||
|
||||
const variables = {
|
||||
name: createProjectFields.name,
|
||||
description: createProjectFields.description,
|
||||
name: projectFields.name,
|
||||
description: projectFields.description,
|
||||
};
|
||||
|
||||
const response = await this.mutate(query, variables);
|
||||
|
||||
return new Project(response.data.createProject.id, variables.name, variables.description, '', createProjectFields.ownerId);
|
||||
return new Project(response.data.createProject.id, variables.name, variables.description, '', projectFields.ownerId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,7 +53,7 @@
|
||||
width="205px"
|
||||
height="48px"
|
||||
:on-press="onCloseClick"
|
||||
is-white="true"
|
||||
is-transparent="true"
|
||||
/>
|
||||
<VButton
|
||||
label="Update"
|
||||
|
@ -24,7 +24,7 @@
|
||||
label='Cancel'
|
||||
width='205px' height='48px'
|
||||
:on-press='onCloseClick'
|
||||
is-white="true"
|
||||
is-transparent="true"
|
||||
/>
|
||||
<VButton
|
||||
label='Delete'
|
||||
|
@ -36,7 +36,7 @@
|
||||
width="205px"
|
||||
height="48px"
|
||||
:on-press="onCloseClick"
|
||||
is-white="true"
|
||||
is-transparent="true"
|
||||
/>
|
||||
<VButton
|
||||
label="Update"
|
||||
|
@ -46,7 +46,7 @@
|
||||
label="Cancel"
|
||||
width="122px"
|
||||
height="48px"
|
||||
is-white="true"
|
||||
is-transparent="true"
|
||||
:on-press="onClearSelection"
|
||||
/>
|
||||
<span class="header-selected-api-keys__info-text" v-if="!isDeleteClicked">
|
||||
|
@ -28,6 +28,8 @@ export default class VButton extends Vue {
|
||||
@Prop({default: false})
|
||||
private readonly isWhite: boolean;
|
||||
@Prop({default: false})
|
||||
private readonly isTransparent: boolean;
|
||||
@Prop({default: false})
|
||||
private readonly isDeletion: boolean;
|
||||
@Prop({default: false})
|
||||
private readonly isBlueWhite: boolean;
|
||||
@ -45,6 +47,8 @@ export default class VButton extends Vue {
|
||||
|
||||
if (this.isWhite) return 'container white';
|
||||
|
||||
if (this.isTransparent) return 'container transparent';
|
||||
|
||||
if (this.isDeletion) return 'container red';
|
||||
|
||||
if (this.isBlueWhite) return 'container blue-white';
|
||||
@ -55,7 +59,7 @@ export default class VButton extends Vue {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.white,
|
||||
.transparent,
|
||||
.red {
|
||||
background-color: transparent !important;
|
||||
border: 1px solid #afb7c1 !important;
|
||||
@ -65,6 +69,15 @@ export default class VButton extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
.white {
|
||||
background-color: #fff !important;
|
||||
border: 1px solid #afb7c1 !important;
|
||||
|
||||
.label {
|
||||
color: #354049 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.blue-white {
|
||||
background-color: #fff !important;
|
||||
border: 2px solid #2683ff !important;
|
||||
@ -102,8 +115,9 @@ export default class VButton extends Vue {
|
||||
&:hover {
|
||||
background-color: #0059d0;
|
||||
|
||||
&.white,
|
||||
&.blue-white {
|
||||
&.transparent,
|
||||
&.blue-white,
|
||||
&.white {
|
||||
box-shadow: none !important;
|
||||
background-color: #2683ff !important;
|
||||
border: 1px solid #2683ff !important;
|
||||
|
134
web/satellite/src/components/navigation/EditProjectDropdown.vue
Normal file
134
web/satellite/src/components/navigation/EditProjectDropdown.vue
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="edit-project">
|
||||
<div class="edit-project__selection-area" :class="{ active: isDropdownShown }" @click.stop.prevent="toggleDropdown">
|
||||
<h1 class="edit-project__selection-area__name">{{ projectName }}</h1>
|
||||
<DotsImage class="edit-project__selection-area__image"/>
|
||||
</div>
|
||||
<div class="edit-project__dropdown" v-if="isDropdownShown" v-click-outside="closeDropdown">
|
||||
<div class="edit-project__dropdown__choice" @click.stop.prevent="onEditProjectClick">
|
||||
<EditImage/>
|
||||
<p class="edit-project__dropdown__choice__label">Edit Details</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import DotsImage from '@/../static/images/navigation/dots.svg';
|
||||
import EditImage from '@/../static/images/navigation/edit.svg';
|
||||
|
||||
import { RouteConfig } from '@/router';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
DotsImage,
|
||||
EditImage,
|
||||
},
|
||||
})
|
||||
export default class EditProjectDropdown extends Vue {
|
||||
public isDropdownShown: boolean = false;
|
||||
|
||||
/**
|
||||
* Returns selected project's name.
|
||||
*/
|
||||
public get projectName(): string {
|
||||
return this.$store.getters.selectedProject.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to edit project details page.
|
||||
*/
|
||||
public onEditProjectClick(): void {
|
||||
this.closeDropdown();
|
||||
this.$router.push(RouteConfig.EditProjectDetails.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles dropdown visibility.
|
||||
*/
|
||||
public toggleDropdown(): void {
|
||||
this.isDropdownShown = !this.isDropdownShown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes dropdown.
|
||||
*/
|
||||
public closeDropdown(): void {
|
||||
this.isDropdownShown = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.edit-project {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
width: 190px;
|
||||
position: relative;
|
||||
margin: 0 0 30px 13px;
|
||||
|
||||
&__selection-area {
|
||||
width: calc(100% - 25px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 15px 10px 10px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
|
||||
&__name {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
color: #000;
|
||||
margin: 0 5px 0 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
&__image {
|
||||
min-width: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
&__dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 5px);
|
||||
left: 0;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 8px 34px rgba(161, 173, 185, 0.41);
|
||||
border-radius: 6px;
|
||||
padding: 5px 0;
|
||||
width: 190px;
|
||||
z-index: 1;
|
||||
|
||||
&__choice {
|
||||
background-color: #fff;
|
||||
width: calc(100% - 32px);
|
||||
padding: 10px 16px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&__label {
|
||||
margin: 0 0 0 10px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
color: #354049;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: rgba(245, 246, 250, 0.7);
|
||||
}
|
||||
</style>
|
@ -3,6 +3,7 @@
|
||||
|
||||
<template>
|
||||
<div class="navigation-area" v-if="!isOnboardingTour">
|
||||
<EditProjectDropdown/>
|
||||
<router-link
|
||||
:aria-label="navItem.name"
|
||||
class="navigation-area__item-container"
|
||||
@ -21,6 +22,8 @@
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import EditProjectDropdown from '@/components/navigation/EditProjectDropdown.vue';
|
||||
|
||||
import ApiKeysIcon from '@/../static/images/navigation/apiKeys.svg';
|
||||
import DashboardIcon from '@/../static/images/navigation/dashboard.svg';
|
||||
import TeamIcon from '@/../static/images/navigation/team.svg';
|
||||
@ -33,6 +36,7 @@ import { NavigationLink } from '@/types/navigation';
|
||||
DashboardIcon,
|
||||
ApiKeysIcon,
|
||||
TeamIcon,
|
||||
EditProjectDropdown,
|
||||
},
|
||||
})
|
||||
export default class NavigationArea extends Vue {
|
||||
|
@ -66,7 +66,7 @@ import { API_KEYS_ACTIONS } from '@/store/modules/apiKeys';
|
||||
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
|
||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { CreateProjectFields } from '@/types/projects';
|
||||
import { ProjectFields } from '@/types/projects';
|
||||
import { PM_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
|
||||
|
||||
@ -109,7 +109,7 @@ export default class CreateProjectStep extends Vue {
|
||||
this.isLoading = true;
|
||||
this.projectName = this.projectName.trim();
|
||||
|
||||
const project = new CreateProjectFields(
|
||||
const project = new ProjectFields(
|
||||
this.projectName,
|
||||
this.description,
|
||||
this.$store.getters.user.id,
|
||||
|
@ -37,7 +37,7 @@
|
||||
width="210px"
|
||||
height="48px"
|
||||
:on-press="onCancelClick"
|
||||
is-white="true"
|
||||
is-transparent="true"
|
||||
/>
|
||||
<VButton
|
||||
label="Create Project +"
|
||||
@ -69,7 +69,7 @@ import { API_KEYS_ACTIONS } from '@/store/modules/apiKeys';
|
||||
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
|
||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { CreateProjectFields } from '@/types/projects';
|
||||
import { ProjectFields } from '@/types/projects';
|
||||
import {
|
||||
APP_STATE_ACTIONS,
|
||||
PM_ACTIONS,
|
||||
@ -126,7 +126,7 @@ export default class NewProjectPopup extends Vue {
|
||||
this.isLoading = true;
|
||||
this.projectName = this.projectName.trim();
|
||||
|
||||
const project = new CreateProjectFields(
|
||||
const project = new ProjectFields(
|
||||
this.projectName,
|
||||
this.description,
|
||||
this.$store.getters.user.id,
|
||||
|
@ -30,7 +30,7 @@
|
||||
width="205px"
|
||||
height="48px"
|
||||
:on-press="onCloseClick"
|
||||
is-white="true"
|
||||
is-transparent="true"
|
||||
/>
|
||||
<VButton
|
||||
label="Delete"
|
||||
|
315
web/satellite/src/components/project/EditProjectDetails.vue
Normal file
315
web/satellite/src/components/project/EditProjectDetails.vue
Normal file
@ -0,0 +1,315 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="project-details">
|
||||
<div class="project-details__wrapper">
|
||||
<p class="project-details__wrapper__back" @click.stop.prevent="onBackClick"><- Back</p>
|
||||
<div class="project-details__wrapper__container">
|
||||
<h1 class="project-details__wrapper__container__title">Project Details</h1>
|
||||
<p class="project-details__wrapper__container__label">Name</p>
|
||||
<div class="project-details__wrapper__container__name-area" v-if="!isNameEditing">
|
||||
<p class="project-details__wrapper__container__name-area__name">{{ storedProject.name }}</p>
|
||||
<VButton
|
||||
label="Edit"
|
||||
width="64px"
|
||||
height="28px"
|
||||
:on-press="toggleNameEditing"
|
||||
is-white="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="project-details__wrapper__container__name-editing" v-if="isNameEditing">
|
||||
<input
|
||||
class="project-details__wrapper__container__name-editing__input"
|
||||
placeholder="Enter a name for your project"
|
||||
@input="onNameInput"
|
||||
@change="onNameInput"
|
||||
v-model="nameValue"
|
||||
/>
|
||||
<span class="project-details__wrapper__container__name-editing__limit">{{ nameValue.length }}/{{ nameLength }}</span>
|
||||
<VButton
|
||||
class="project-details__wrapper__container__name-editing__save-button"
|
||||
label="Save"
|
||||
width="66px"
|
||||
height="30px"
|
||||
:on-press="onSaveNameButtonClick"
|
||||
/>
|
||||
</div>
|
||||
<p class="project-details__wrapper__container__label">Description</p>
|
||||
<div class="project-details__wrapper__container__description-area" v-if="!isDescriptionEditing">
|
||||
<p class="project-details__wrapper__container__description-area__description">{{ displayedDescription }}</p>
|
||||
<VButton
|
||||
label="Edit"
|
||||
width="64px"
|
||||
height="28px"
|
||||
:on-press="toggleDescriptionEditing"
|
||||
is-white="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="project-details__wrapper__container__description-editing" v-if="isDescriptionEditing">
|
||||
<input
|
||||
class="project-details__wrapper__container__description-editing__input"
|
||||
placeholder="Enter a description for your project"
|
||||
@input="onDescriptionInput"
|
||||
@change="onDescriptionInput"
|
||||
v-model="descriptionValue"
|
||||
/>
|
||||
<span class="project-details__wrapper__container__description-editing__limit">{{ descriptionValue.length }}/{{ descriptionLength }}</span>
|
||||
<VButton
|
||||
class="project-details__wrapper__container__description-editing__save-button"
|
||||
label="Save"
|
||||
width="66px"
|
||||
height="30px"
|
||||
:on-press="onSaveDescriptionButtonClick"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import HeaderedInput from '@/components/common/HeaderedInput.vue';
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
|
||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { MAX_DESCRIPTION_LENGTH, MAX_NAME_LENGTH, Project, ProjectFields } from '@/types/projects';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
VButton,
|
||||
HeaderedInput,
|
||||
},
|
||||
})
|
||||
export default class EditProjectDetails extends Vue {
|
||||
public isNameEditing: boolean = false;
|
||||
public isDescriptionEditing: boolean = false;
|
||||
public nameValue: string = '';
|
||||
public descriptionValue: string = '';
|
||||
public nameLength: number = MAX_NAME_LENGTH;
|
||||
public descriptionLength: number = MAX_DESCRIPTION_LENGTH;
|
||||
|
||||
/**
|
||||
* Returns selected project from store.
|
||||
*/
|
||||
public get storedProject(): Project {
|
||||
return this.$store.getters.selectedProject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns displayed project description on UI.
|
||||
*/
|
||||
public get displayedDescription(): string {
|
||||
return this.storedProject.description ?
|
||||
this.storedProject.description :
|
||||
'No description yet. Please enter some information if any.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers on name input.
|
||||
*/
|
||||
public onNameInput({ target }): void {
|
||||
if (target.value.length < MAX_NAME_LENGTH) {
|
||||
this.nameValue = target.value;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.nameValue = target.value.slice(0, MAX_NAME_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers on description input.
|
||||
*/
|
||||
public onDescriptionInput({ target }): void {
|
||||
if (target.value.length < MAX_DESCRIPTION_LENGTH) {
|
||||
this.descriptionValue = target.value;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.descriptionValue = target.value.slice(0, MAX_DESCRIPTION_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates project name.
|
||||
*/
|
||||
public async onSaveNameButtonClick(): Promise<void> {
|
||||
try {
|
||||
const updatedProject = new ProjectFields(this.nameValue, '');
|
||||
updatedProject.checkName();
|
||||
|
||||
await this.$store.dispatch(PROJECTS_ACTIONS.UPDATE_NAME, updatedProject);
|
||||
} catch (error) {
|
||||
await this.$notify.error(`Unable to update project name. ${error.message}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.toggleNameEditing();
|
||||
await this.$notify.success('Project name updated successfully!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates project description.
|
||||
*/
|
||||
public async onSaveDescriptionButtonClick(): Promise<void> {
|
||||
try {
|
||||
const updatedProject = new ProjectFields('', this.descriptionValue);
|
||||
await this.$store.dispatch(PROJECTS_ACTIONS.UPDATE_DESCRIPTION, updatedProject);
|
||||
} catch (error) {
|
||||
await this.$notify.error(`Unable to update project description. ${error.message}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.toggleDescriptionEditing();
|
||||
await this.$notify.success('Project description updated successfully!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles project name editing state.
|
||||
*/
|
||||
public toggleNameEditing(): void {
|
||||
this.isNameEditing = !this.isNameEditing;
|
||||
this.nameValue = this.storedProject.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles project description editing state.
|
||||
*/
|
||||
public toggleDescriptionEditing(): void {
|
||||
this.isDescriptionEditing = !this.isDescriptionEditing;
|
||||
this.descriptionValue = this.storedProject.description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to previous route.
|
||||
*/
|
||||
public onBackClick(): void {
|
||||
const PREVIOUS_ROUTE_NUMBER = -1;
|
||||
|
||||
this.$router.go(PREVIOUS_ROUTE_NUMBER);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.project-details {
|
||||
padding: 45px 0;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&__wrapper {
|
||||
width: 727px;
|
||||
|
||||
&__back {
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 23px;
|
||||
color: #2582ff;
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
|
||||
&__container {
|
||||
padding: 50px;
|
||||
width: calc(100% - 100px);
|
||||
border-radius: 6px;
|
||||
background-color: #fff;
|
||||
|
||||
&__title {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 22px;
|
||||
line-height: 27px;
|
||||
color: #384b65;
|
||||
margin: 0 0 35px 0;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
color: #384b65;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
&__name-area,
|
||||
&__description-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 11px 7px 11px 23px;
|
||||
width: calc(100% - 30px);
|
||||
background: #f5f6fa;
|
||||
border-radius: 6px;
|
||||
|
||||
&__name,
|
||||
&__description {
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
line-height: 19px;
|
||||
color: #384b65;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&__name-area {
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
&__name-editing,
|
||||
&__description-editing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: calc(100% - 7px);
|
||||
border-radius: 6px;
|
||||
background-color: #f5f6fa;
|
||||
padding-right: 7px;
|
||||
|
||||
&__input {
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
flex: 1;
|
||||
height: 48px;
|
||||
width: available;
|
||||
text-indent: 20px;
|
||||
background-color: #f5f6fa;
|
||||
border-color: #f5f6fa;
|
||||
border-radius: 6px;
|
||||
|
||||
&::placeholder {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
&__limit {
|
||||
font-size: 14px;
|
||||
line-height: 21px;
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
margin: 0 0 0 15px;
|
||||
min-width: 53px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&__save-button {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
&__name-editing {
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -14,7 +14,6 @@
|
||||
Request Limit Increase ->
|
||||
</a>
|
||||
</div>
|
||||
<ProjectDetails/>
|
||||
<ProjectUsage/>
|
||||
<BucketArea/>
|
||||
</div>
|
||||
@ -24,7 +23,6 @@
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import BucketArea from '@/components/project/buckets/BucketArea.vue';
|
||||
import ProjectDetails from '@/components/project/ProjectDetails.vue';
|
||||
import ProjectUsage from '@/components/project/usage/ProjectUsage.vue';
|
||||
|
||||
import { RouteConfig } from '@/router';
|
||||
@ -35,7 +33,6 @@ import { MetaUtils } from '@/utils/meta';
|
||||
@Component({
|
||||
components: {
|
||||
BucketArea,
|
||||
ProjectDetails,
|
||||
ProjectUsage,
|
||||
},
|
||||
})
|
||||
|
@ -1,208 +0,0 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="project-details">
|
||||
<div class="project-details__title-area">
|
||||
<h1 class="project-details__title-area__title">{{ name }}</h1>
|
||||
<span class="project-details__title-area__edit" v-if="!isEditing" @click="toggleEditing">
|
||||
Edit Description
|
||||
</span>
|
||||
</div>
|
||||
<p class="project-details__description" v-if="!isEditing">{{ displayedDescription }}</p>
|
||||
<div class="project-details__editing" v-else>
|
||||
<input
|
||||
class="project-details__editing__input"
|
||||
placeholder="Enter a description for your project. Descriptions are limited to 100 characters."
|
||||
@input="onInput"
|
||||
@change="onInput"
|
||||
v-model="value"
|
||||
/>
|
||||
<span class="project-details__editing__limit">{{value.length}}/{{MAX_SYMBOLS}}</span>
|
||||
<VButton
|
||||
class="project-details__editing__cancel-button"
|
||||
label="Cancel"
|
||||
width="73px"
|
||||
height="33px"
|
||||
:on-press="toggleEditing"
|
||||
is-white="true"
|
||||
/>
|
||||
<VButton
|
||||
class="project-details__editing__save-button"
|
||||
label="Save"
|
||||
width="75px"
|
||||
height="35px"
|
||||
:on-press="onSaveButtonClick"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import HeaderedInput from '@/components/common/HeaderedInput.vue';
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
|
||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { UpdateProjectFields } from '@/types/projects';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
VButton,
|
||||
HeaderedInput,
|
||||
},
|
||||
})
|
||||
export default class ProjectDetails extends Vue {
|
||||
public readonly MAX_SYMBOLS: number = 100;
|
||||
|
||||
public isEditing: boolean = false;
|
||||
public value: string = '';
|
||||
|
||||
/**
|
||||
* Returns selected project name.
|
||||
*/
|
||||
public get name(): string {
|
||||
return this.$store.getters.selectedProject.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected project description from store.
|
||||
*/
|
||||
public get storedDescription(): string {
|
||||
return this.$store.getters.selectedProject.description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns displayed project description on UI.
|
||||
*/
|
||||
public get displayedDescription(): string {
|
||||
return this.storedDescription ?
|
||||
this.storedDescription :
|
||||
'No description yet. Please enter some information about the project if any.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers on input.
|
||||
*/
|
||||
public onInput({ target }): void {
|
||||
if (target.value.length < this.MAX_SYMBOLS) {
|
||||
this.value = target.value;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.value = target.value.slice(0, this.MAX_SYMBOLS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates project description.
|
||||
*/
|
||||
public async onSaveButtonClick(): Promise<void> {
|
||||
try {
|
||||
const updatedProject = new UpdateProjectFields('', this.value);
|
||||
await this.$store.dispatch(PROJECTS_ACTIONS.UPDATE_DESCRIPTION, updatedProject);
|
||||
} catch (error) {
|
||||
await this.$notify.error(`Unable to update project description. ${error.message}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.toggleEditing();
|
||||
await this.$notify.success('Project updated successfully!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles project description editing state.
|
||||
*/
|
||||
public toggleEditing(): void {
|
||||
this.isEditing = !this.isEditing;
|
||||
this.value = this.storedDescription;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
h1,
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.project-details {
|
||||
padding: 35px;
|
||||
width: calc(100% - 70px);
|
||||
font-family: 'font_regular', sans-serif;
|
||||
background-color: #fff;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 36px;
|
||||
|
||||
&__title-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&__title {
|
||||
font-size: 22px;
|
||||
line-height: 27px;
|
||||
color: #000;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
&__edit {
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
color: #909ba8;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #2683ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
margin-top: 30px;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #354049;
|
||||
width: available;
|
||||
overflow-y: scroll;
|
||||
word-break: break-word;
|
||||
max-height: 100px;
|
||||
}
|
||||
|
||||
&__editing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
width: calc(100% - 7px);
|
||||
border-radius: 6px;
|
||||
background-color: #f5f6fa;
|
||||
padding-right: 7px;
|
||||
|
||||
&__input {
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
flex: 1;
|
||||
height: 48px;
|
||||
width: available;
|
||||
text-indent: 20px;
|
||||
background-color: #f5f6fa;
|
||||
border-color: #f5f6fa;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
&__limit {
|
||||
font-size: 14px;
|
||||
line-height: 21px;
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
&__save-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -46,7 +46,7 @@
|
||||
width='205px'
|
||||
height='48px'
|
||||
:on-press="onClose"
|
||||
is-white="true"
|
||||
is-transparent="true"
|
||||
/>
|
||||
<VButton
|
||||
label='Add Team Members'
|
||||
|
@ -38,7 +38,7 @@
|
||||
label="Cancel"
|
||||
width="122px"
|
||||
height="48px"
|
||||
is-white="true"
|
||||
is-transparent="true"
|
||||
:on-press="onClearSelection"
|
||||
/>
|
||||
<span class="header-selected-members__info-text"><b>{{selectedProjectMembersCount}}</b> users selected</span>
|
||||
@ -58,7 +58,7 @@
|
||||
label="Cancel"
|
||||
width="122px"
|
||||
height="48px"
|
||||
is-white="true"
|
||||
is-transparent="true"
|
||||
:on-press="onClearSelection"
|
||||
/>
|
||||
</div>
|
||||
|
@ -13,6 +13,7 @@ import ApiKeysArea from '@/components/apiKeys/ApiKeysArea.vue';
|
||||
import Page404 from '@/components/errors/Page404.vue';
|
||||
import OnboardingTourArea from '@/components/onboardingTour/OnboardingTourArea.vue';
|
||||
import CreateProject from '@/components/project/CreateProject.vue';
|
||||
import EditProjectDetails from '@/components/project/EditProjectDetails.vue';
|
||||
import ProjectDashboard from '@/components/project/ProjectDashboard.vue';
|
||||
import ProjectMembersArea from '@/components/team/ProjectMembersArea.vue';
|
||||
|
||||
@ -40,6 +41,7 @@ export abstract class RouteConfig {
|
||||
public static ApiKeys = new NavigationLink('/api-keys', 'API Keys');
|
||||
public static OnboardingTour = new NavigationLink('/onboarding-tour', 'Onboarding Tour');
|
||||
public static CreateProject = new NavigationLink('/create-project', 'Create Project');
|
||||
public static EditProjectDetails = new NavigationLink('/edit-project-details', 'Edit Project Details');
|
||||
|
||||
// child paths
|
||||
public static Settings = new NavigationLink('settings', 'Settings');
|
||||
@ -158,6 +160,11 @@ export const router = new Router({
|
||||
name: RouteConfig.CreateProject.name,
|
||||
component: CreateProject,
|
||||
},
|
||||
{
|
||||
path: RouteConfig.EditProjectDetails.path,
|
||||
name: RouteConfig.EditProjectDetails.name,
|
||||
component: EditProjectDetails,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -3,11 +3,10 @@
|
||||
|
||||
import { StoreModule } from '@/store';
|
||||
import {
|
||||
CreateProjectFields,
|
||||
Project,
|
||||
ProjectFields,
|
||||
ProjectLimits,
|
||||
ProjectsApi,
|
||||
UpdateProjectFields,
|
||||
} from '@/types/projects';
|
||||
|
||||
export const PROJECTS_ACTIONS = {
|
||||
@ -101,10 +100,10 @@ export function makeProjectsModule(api: ProjectsApi): StoreModule<ProjectsState>
|
||||
|
||||
state.selectedProject = selected;
|
||||
},
|
||||
[UPDATE_PROJECT_NAME](state: ProjectsState, fieldsToUpdate: UpdateProjectFields): void {
|
||||
[UPDATE_PROJECT_NAME](state: ProjectsState, fieldsToUpdate: ProjectFields): void {
|
||||
state.selectedProject.name = fieldsToUpdate.name;
|
||||
},
|
||||
[UPDATE_PROJECT_DESCRIPTION](state: ProjectsState, fieldsToUpdate: UpdateProjectFields): void {
|
||||
[UPDATE_PROJECT_DESCRIPTION](state: ProjectsState, fieldsToUpdate: ProjectFields): void {
|
||||
state.selectedProject.description = fieldsToUpdate.description;
|
||||
},
|
||||
[REMOVE](state: ProjectsState, projectID: string): void {
|
||||
@ -131,8 +130,8 @@ export function makeProjectsModule(api: ProjectsApi): StoreModule<ProjectsState>
|
||||
|
||||
return projects;
|
||||
},
|
||||
[CREATE]: async function ({commit}: any, createProjectModel: CreateProjectFields): Promise<Project> {
|
||||
const project = await api.create(createProjectModel);
|
||||
[CREATE]: async function ({commit}: any, createProjectFields: ProjectFields): Promise<Project> {
|
||||
const project = await api.create(createProjectFields);
|
||||
|
||||
commit(ADD, project);
|
||||
|
||||
@ -141,12 +140,12 @@ export function makeProjectsModule(api: ProjectsApi): StoreModule<ProjectsState>
|
||||
[SELECT]: function ({commit}: any, projectID: string): void {
|
||||
commit(SELECT_PROJECT, projectID);
|
||||
},
|
||||
[UPDATE_NAME]: async function ({commit, state}: any, fieldsToUpdate: UpdateProjectFields): Promise<void> {
|
||||
[UPDATE_NAME]: async function ({commit, state}: any, fieldsToUpdate: ProjectFields): Promise<void> {
|
||||
await api.update(state.selectedProject.id, fieldsToUpdate.name, state.selectedProject.description);
|
||||
|
||||
commit(UPDATE_PROJECT_NAME, fieldsToUpdate);
|
||||
},
|
||||
[UPDATE_DESCRIPTION]: async function ({commit, state}: any, fieldsToUpdate: UpdateProjectFields): Promise<void> {
|
||||
[UPDATE_DESCRIPTION]: async function ({commit, state}: any, fieldsToUpdate: ProjectFields): Promise<void> {
|
||||
await api.update(state.selectedProject.id, state.selectedProject.name, fieldsToUpdate.description);
|
||||
|
||||
commit(UPDATE_PROJECT_DESCRIPTION, fieldsToUpdate);
|
||||
|
@ -11,7 +11,7 @@ export interface ProjectsApi {
|
||||
* @param createProjectFields - contains project information
|
||||
* @throws Error
|
||||
*/
|
||||
create(createProjectFields: CreateProjectFields): Promise<Project>;
|
||||
create(createProjectFields: ProjectFields): Promise<Project>;
|
||||
/**
|
||||
* Fetch projects.
|
||||
*
|
||||
@ -46,6 +46,16 @@ export interface ProjectsApi {
|
||||
getLimits(projectId: string): Promise<ProjectLimits>;
|
||||
}
|
||||
|
||||
/**
|
||||
* MAX_NAME_LENGTH defines maximum amount of symbols for project name.
|
||||
*/
|
||||
export const MAX_NAME_LENGTH = 20;
|
||||
|
||||
/**
|
||||
* MAX_DESCRIPTION_LENGTH defines maximum amount of symbols for project description.
|
||||
*/
|
||||
export const MAX_DESCRIPTION_LENGTH = 100;
|
||||
|
||||
/**
|
||||
* Project is a type, used for creating new project in backend.
|
||||
*/
|
||||
@ -61,21 +71,9 @@ export class Project {
|
||||
}
|
||||
|
||||
/**
|
||||
* UpdateProjectFields is a type, used for updating project name or description.
|
||||
* ProjectFields is a type, used for creating and updating project.
|
||||
*/
|
||||
export class UpdateProjectFields {
|
||||
public constructor(
|
||||
public name: string,
|
||||
public description: string,
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* CreateProjectFields is a type, used for creating project.
|
||||
*/
|
||||
export class CreateProjectFields {
|
||||
private readonly MAX_NAME_LENGTH = 20;
|
||||
|
||||
export class ProjectFields {
|
||||
public constructor(
|
||||
public name: string = '',
|
||||
public description: string = '',
|
||||
@ -105,7 +103,7 @@ export class CreateProjectFields {
|
||||
* nameHasLessThenTwentySymbols checks if project name has less then 20 symbols.
|
||||
*/
|
||||
private nameHasLessThenTwentySymbols(): void {
|
||||
if (this.name.length > this.MAX_NAME_LENGTH) throw new Error('Name should be less than 21 character!');
|
||||
if (this.name.length > MAX_NAME_LENGTH) throw new Error('Name should be less than 21 character!');
|
||||
}
|
||||
}
|
||||
|
||||
|
5
web/satellite/static/images/navigation/dots.svg
Normal file
5
web/satellite/static/images/navigation/dots.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="3" height="15" viewBox="0 0 3 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.72723 1.36362C2.72723 2.11672 2.11672 2.72723 1.36362 2.72723C0.610511 2.72723 0 2.11672 0 1.36362C0 0.610511 0.610511 0 1.36362 0C2.11672 0 2.72723 0.610511 2.72723 1.36362Z" fill="#2683FF"/>
|
||||
<path d="M2.72723 7.49922C2.72723 8.25232 2.11672 8.86283 1.36362 8.86283C0.610511 8.86283 0 8.25232 0 7.49922C0 6.74611 0.610511 6.1356 1.36362 6.1356C2.11672 6.1356 2.72723 6.74611 2.72723 7.49922Z" fill="#2683FF"/>
|
||||
<path d="M1.36362 15C2.11672 15 2.72723 14.3895 2.72723 13.6364C2.72723 12.8833 2.11672 12.2728 1.36362 12.2728C0.610511 12.2728 0 12.8833 0 13.6364C0 14.3895 0.610511 15 1.36362 15Z" fill="#2683FF"/>
|
||||
</svg>
|
After Width: | Height: | Size: 737 B |
3
web/satellite/static/images/navigation/edit.svg
Normal file
3
web/satellite/static/images/navigation/edit.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.43444 0L12.13 2.69556L3.50422 11.3213L0 12.13L0.808667 8.62578L9.43444 0Z" fill="#2582FF"/>
|
||||
</svg>
|
After Width: | Height: | Size: 211 B |
@ -1,7 +1,7 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { CreateProjectFields, Project, ProjectLimits, ProjectsApi } from '@/types/projects';
|
||||
import { Project, ProjectFields, ProjectLimits, ProjectsApi } from '@/types/projects';
|
||||
|
||||
/**
|
||||
* Mock for ProjectsApi
|
||||
@ -18,7 +18,7 @@ export class ProjectsApiMock implements ProjectsApi {
|
||||
this.mockLimits = mockLimits;
|
||||
}
|
||||
|
||||
create(createProjectFields: CreateProjectFields): Promise<Project> {
|
||||
create(createProjectFields: ProjectFields): Promise<Project> {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,69 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { VNode } from 'vue';
|
||||
import { DirectiveBinding } from 'vue/types/options';
|
||||
import Vuex from 'vuex';
|
||||
|
||||
import EditProjectDropdown from '@/components/navigation/EditProjectDropdown.vue';
|
||||
|
||||
import { makeProjectsModule, PROJECTS_MUTATIONS } from '@/store/modules/projects';
|
||||
import { Project } from '@/types/projects';
|
||||
import { createLocalVue, mount } from '@vue/test-utils';
|
||||
|
||||
import { ProjectsApiMock } from '../mock/api/projects';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
const projectsApi = new ProjectsApiMock();
|
||||
const projectsModule = makeProjectsModule(projectsApi);
|
||||
const store = new Vuex.Store({ modules: { projectsModule }});
|
||||
const project = new Project('id', 'test', 'test', 'test', 'ownedId', false);
|
||||
|
||||
let clickOutsideEvent: EventListener;
|
||||
|
||||
localVue.directive('cli' +
|
||||
'ck-outside', {
|
||||
bind: function (el: HTMLElement, binding: DirectiveBinding, vnode: VNode) {
|
||||
clickOutsideEvent = function(event: Event): void {
|
||||
if (el === event.target) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vnode.context) {
|
||||
vnode.context[binding.expression](event);
|
||||
}
|
||||
};
|
||||
|
||||
document.body.addEventListener('click', clickOutsideEvent);
|
||||
},
|
||||
unbind: function(): void {
|
||||
document.body.removeEventListener('click', clickOutsideEvent);
|
||||
},
|
||||
});
|
||||
|
||||
localVue.use(Vuex);
|
||||
|
||||
store.commit(PROJECTS_MUTATIONS.ADD, project);
|
||||
store.commit(PROJECTS_MUTATIONS.SELECT_PROJECT, project.id);
|
||||
|
||||
describe('EditProjectDropdown', () => {
|
||||
it('renders correctly', (): void => {
|
||||
const wrapper = mount(EditProjectDropdown, {
|
||||
store,
|
||||
localVue,
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('dropdown opens correctly', async (): Promise<void> => {
|
||||
const wrapper = mount(EditProjectDropdown, {
|
||||
store,
|
||||
localVue,
|
||||
});
|
||||
|
||||
await wrapper.find('.edit-project__selection-area').trigger('click');
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,33 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`EditProjectDropdown dropdown opens correctly 1`] = `
|
||||
<div class="edit-project">
|
||||
<div class="edit-project__selection-area active">
|
||||
<h1 class="edit-project__selection-area__name">test</h1> <svg width="3" height="15" viewBox="0 0 3 15" fill="none" xmlns="http://www.w3.org/2000/svg" class="edit-project__selection-area__image">
|
||||
<path d="M2.72723 1.36362C2.72723 2.11672 2.11672 2.72723 1.36362 2.72723C0.610511 2.72723 0 2.11672 0 1.36362C0 0.610511 0.610511 0 1.36362 0C2.11672 0 2.72723 0.610511 2.72723 1.36362Z" fill="#2683FF"></path>
|
||||
<path d="M2.72723 7.49922C2.72723 8.25232 2.11672 8.86283 1.36362 8.86283C0.610511 8.86283 0 8.25232 0 7.49922C0 6.74611 0.610511 6.1356 1.36362 6.1356C2.11672 6.1356 2.72723 6.74611 2.72723 7.49922Z" fill="#2683FF"></path>
|
||||
<path d="M1.36362 15C2.11672 15 2.72723 14.3895 2.72723 13.6364C2.72723 12.8833 2.11672 12.2728 1.36362 12.2728C0.610511 12.2728 0 12.8833 0 13.6364C0 14.3895 0.610511 15 1.36362 15Z" fill="#2683FF"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="edit-project__dropdown">
|
||||
<div class="edit-project__dropdown__choice"><svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.43444 0L12.13 2.69556L3.50422 11.3213L0 12.13L0.808667 8.62578L9.43444 0Z" fill="#2582FF"></path>
|
||||
</svg>
|
||||
<p class="edit-project__dropdown__choice__label">Edit Details</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`EditProjectDropdown renders correctly 1`] = `
|
||||
<div class="edit-project">
|
||||
<div class="edit-project__selection-area">
|
||||
<h1 class="edit-project__selection-area__name">test</h1> <svg width="3" height="15" viewBox="0 0 3 15" fill="none" xmlns="http://www.w3.org/2000/svg" class="edit-project__selection-area__image">
|
||||
<path d="M2.72723 1.36362C2.72723 2.11672 2.11672 2.72723 1.36362 2.72723C0.610511 2.72723 0 2.11672 0 1.36362C0 0.610511 0.610511 0 1.36362 0C2.11672 0 2.72723 0.610511 2.72723 1.36362Z" fill="#2683FF"></path>
|
||||
<path d="M2.72723 7.49922C2.72723 8.25232 2.11672 8.86283 1.36362 8.86283C0.610511 8.86283 0 8.25232 0 7.49922C0 6.74611 0.610511 6.1356 1.36362 6.1356C2.11672 6.1356 2.72723 6.74611 2.72723 7.49922Z" fill="#2683FF"></path>
|
||||
<path d="M1.36362 15C2.11672 15 2.72723 14.3895 2.72723 13.6364C2.72723 12.8833 2.11672 12.2728 1.36362 12.2728C0.610511 12.2728 0 12.8833 0 13.6364C0 14.3895 0.610511 15 1.36362 15Z" fill="#2683FF"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
@ -4,6 +4,7 @@ exports[`NavigationArea snapshot not changed during onboarding tour 1`] = ``;
|
||||
|
||||
exports[`NavigationArea snapshot not changed with project 1`] = `
|
||||
<div class="navigation-area">
|
||||
<editprojectdropdown-stub></editprojectdropdown-stub>
|
||||
<router-link-stub to="/project-dashboard" tag="a" ariacurrentvalue="page" event="click" aria-label="Dashboard" class="navigation-area__item-container">
|
||||
<div class="navigation-area__item-container__link">
|
||||
<anonymous-stub></anonymous-stub>
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import Vuex from 'vuex';
|
||||
|
||||
import ProjectDetails from '@/components/project/ProjectDetails.vue';
|
||||
import EditProjectDetails from '@/components/project/EditProjectDetails.vue';
|
||||
|
||||
import { makeProjectsModule, PROJECTS_MUTATIONS } from '@/store/modules/projects';
|
||||
import { Project } from '@/types/projects';
|
||||
@ -24,12 +24,12 @@ const projectsModule = makeProjectsModule(projectsApi);
|
||||
const store = new Vuex.Store({ modules: { projectsModule }});
|
||||
const project = new Project('id', 'test', 'test', 'test', 'ownedId', false);
|
||||
|
||||
describe('ProjectDetails.vue', () => {
|
||||
describe('EditProjectDetails.vue', () => {
|
||||
it('renders correctly', (): void => {
|
||||
store.commit(PROJECTS_MUTATIONS.ADD, project);
|
||||
store.commit(PROJECTS_MUTATIONS.SELECT_PROJECT, project.id);
|
||||
|
||||
const wrapper = mount(ProjectDetails, {
|
||||
const wrapper = mount(EditProjectDetails, {
|
||||
store,
|
||||
localVue,
|
||||
});
|
||||
@ -37,20 +37,41 @@ describe('ProjectDetails.vue', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('editing works correctly', async (): Promise<void> => {
|
||||
const wrapper = mount(ProjectDetails, {
|
||||
it('editing name works correctly', async (): Promise<void> => {
|
||||
const wrapper = mount(EditProjectDetails, {
|
||||
store,
|
||||
localVue,
|
||||
});
|
||||
|
||||
await wrapper.vm.toggleEditing();
|
||||
await wrapper.vm.toggleNameEditing();
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
wrapper.vm.$data.value = 'new description';
|
||||
await wrapper.vm.onSaveButtonClick();
|
||||
const newName = 'new name';
|
||||
|
||||
wrapper.vm.$data.nameValue = newName;
|
||||
await wrapper.vm.onSaveNameButtonClick();
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
await expect(wrapper.vm.storedDescription).toMatch('new description');
|
||||
await expect(store.getters.selectedProject.name).toMatch(newName);
|
||||
});
|
||||
|
||||
it('editing description works correctly', async (): Promise<void> => {
|
||||
const wrapper = mount(EditProjectDetails, {
|
||||
store,
|
||||
localVue,
|
||||
});
|
||||
|
||||
await wrapper.vm.toggleDescriptionEditing();
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
const newDescription = 'new description';
|
||||
|
||||
wrapper.vm.$data.descriptionValue = newDescription;
|
||||
await wrapper.vm.onSaveDescriptionButtonClick();
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
await expect(store.getters.selectedProject.description).toMatch(newDescription);
|
||||
});
|
||||
});
|
@ -31,7 +31,7 @@ exports[`CreateProject.vue renders correctly 1`] = `
|
||||
<!---->
|
||||
</div>
|
||||
<div class="create-project-area__container__button-container">
|
||||
<div class="container white" style="width: 210px; height: 48px;"><span class="label">Cancel</span></div>
|
||||
<div class="container transparent" style="width: 210px; height: 48px;"><span class="label">Cancel</span></div>
|
||||
<div class="container disabled" style="width: 210px; height: 48px;"><span class="label">Create Project +</span></div>
|
||||
</div>
|
||||
<!---->
|
||||
|
@ -0,0 +1,114 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`EditProjectDetails.vue editing description works correctly 1`] = `
|
||||
<div class="project-details">
|
||||
<div class="project-details__wrapper">
|
||||
<p class="project-details__wrapper__back"><- Back</p>
|
||||
<div class="project-details__wrapper__container">
|
||||
<h1 class="project-details__wrapper__container__title">Project Details</h1>
|
||||
<p class="project-details__wrapper__container__label">Name</p>
|
||||
<div class="project-details__wrapper__container__name-area">
|
||||
<p class="project-details__wrapper__container__name-area__name">new name</p>
|
||||
<div class="container white" style="width: 64px; height: 28px;"><span class="label">Edit</span></div>
|
||||
</div>
|
||||
<!---->
|
||||
<p class="project-details__wrapper__container__label">Description</p>
|
||||
<!---->
|
||||
<div class="project-details__wrapper__container__description-editing"><input placeholder="Enter a description for your project" class="project-details__wrapper__container__description-editing__input"> <span class="project-details__wrapper__container__description-editing__limit">4/100</span>
|
||||
<div class="project-details__wrapper__container__description-editing__save-button container" style="width: 66px; height: 30px;"><span class="label">Save</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`EditProjectDetails.vue editing description works correctly 2`] = `
|
||||
<div class="project-details">
|
||||
<div class="project-details__wrapper">
|
||||
<p class="project-details__wrapper__back"><- Back</p>
|
||||
<div class="project-details__wrapper__container">
|
||||
<h1 class="project-details__wrapper__container__title">Project Details</h1>
|
||||
<p class="project-details__wrapper__container__label">Name</p>
|
||||
<div class="project-details__wrapper__container__name-area">
|
||||
<p class="project-details__wrapper__container__name-area__name">new name</p>
|
||||
<div class="container white" style="width: 64px; height: 28px;"><span class="label">Edit</span></div>
|
||||
</div>
|
||||
<!---->
|
||||
<p class="project-details__wrapper__container__label">Description</p>
|
||||
<div class="project-details__wrapper__container__description-area">
|
||||
<p class="project-details__wrapper__container__description-area__description">new description</p>
|
||||
<div class="container white" style="width: 64px; height: 28px;"><span class="label">Edit</span></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`EditProjectDetails.vue editing name works correctly 1`] = `
|
||||
<div class="project-details">
|
||||
<div class="project-details__wrapper">
|
||||
<p class="project-details__wrapper__back"><- Back</p>
|
||||
<div class="project-details__wrapper__container">
|
||||
<h1 class="project-details__wrapper__container__title">Project Details</h1>
|
||||
<p class="project-details__wrapper__container__label">Name</p>
|
||||
<!---->
|
||||
<div class="project-details__wrapper__container__name-editing"><input placeholder="Enter a name for your project" class="project-details__wrapper__container__name-editing__input"> <span class="project-details__wrapper__container__name-editing__limit">4/20</span>
|
||||
<div class="project-details__wrapper__container__name-editing__save-button container" style="width: 66px; height: 30px;"><span class="label">Save</span></div>
|
||||
</div>
|
||||
<p class="project-details__wrapper__container__label">Description</p>
|
||||
<div class="project-details__wrapper__container__description-area">
|
||||
<p class="project-details__wrapper__container__description-area__description">test</p>
|
||||
<div class="container white" style="width: 64px; height: 28px;"><span class="label">Edit</span></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`EditProjectDetails.vue editing name works correctly 2`] = `
|
||||
<div class="project-details">
|
||||
<div class="project-details__wrapper">
|
||||
<p class="project-details__wrapper__back"><- Back</p>
|
||||
<div class="project-details__wrapper__container">
|
||||
<h1 class="project-details__wrapper__container__title">Project Details</h1>
|
||||
<p class="project-details__wrapper__container__label">Name</p>
|
||||
<div class="project-details__wrapper__container__name-area">
|
||||
<p class="project-details__wrapper__container__name-area__name">new name</p>
|
||||
<div class="container white" style="width: 64px; height: 28px;"><span class="label">Edit</span></div>
|
||||
</div>
|
||||
<!---->
|
||||
<p class="project-details__wrapper__container__label">Description</p>
|
||||
<div class="project-details__wrapper__container__description-area">
|
||||
<p class="project-details__wrapper__container__description-area__description">test</p>
|
||||
<div class="container white" style="width: 64px; height: 28px;"><span class="label">Edit</span></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`EditProjectDetails.vue renders correctly 1`] = `
|
||||
<div class="project-details">
|
||||
<div class="project-details__wrapper">
|
||||
<p class="project-details__wrapper__back"><- Back</p>
|
||||
<div class="project-details__wrapper__container">
|
||||
<h1 class="project-details__wrapper__container__title">Project Details</h1>
|
||||
<p class="project-details__wrapper__container__label">Name</p>
|
||||
<div class="project-details__wrapper__container__name-area">
|
||||
<p class="project-details__wrapper__container__name-area__name">test</p>
|
||||
<div class="container white" style="width: 64px; height: 28px;"><span class="label">Edit</span></div>
|
||||
</div>
|
||||
<!---->
|
||||
<p class="project-details__wrapper__container__label">Description</p>
|
||||
<div class="project-details__wrapper__container__description-area">
|
||||
<p class="project-details__wrapper__container__description-area__description">test</p>
|
||||
<div class="container white" style="width: 64px; height: 28px;"><span class="label">Edit</span></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -7,7 +7,6 @@ exports[`ProjectDashboard.vue renders correctly 1`] = `
|
||||
Request Limit Increase ->
|
||||
</a>
|
||||
</div>
|
||||
<projectdetails-stub></projectdetails-stub>
|
||||
<projectusage-stub></projectusage-stub>
|
||||
<bucketarea-stub></bucketarea-stub>
|
||||
</div>
|
||||
|
@ -1,36 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ProjectDetails.vue editing works correctly 1`] = `
|
||||
<div class="project-details">
|
||||
<div class="project-details__title-area">
|
||||
<h1 class="project-details__title-area__title">test</h1>
|
||||
<!---->
|
||||
</div>
|
||||
<div class="project-details__editing"><input placeholder="Enter a description for your project. Descriptions are limited to 100 characters." class="project-details__editing__input"> <span class="project-details__editing__limit">4/100</span>
|
||||
<div class="project-details__editing__cancel-button container white" style="width: 73px; height: 33px;"><span class="label">Cancel</span></div>
|
||||
<div class="project-details__editing__save-button container" style="width: 75px; height: 35px;"><span class="label">Save</span></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ProjectDetails.vue editing works correctly 2`] = `
|
||||
<div class="project-details">
|
||||
<div class="project-details__title-area">
|
||||
<h1 class="project-details__title-area__title">test</h1> <span class="project-details__title-area__edit">
|
||||
Edit Description
|
||||
</span>
|
||||
</div>
|
||||
<p class="project-details__description">new description</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ProjectDetails.vue renders correctly 1`] = `
|
||||
<div class="project-details">
|
||||
<div class="project-details__title-area">
|
||||
<h1 class="project-details__title-area__title">test</h1> <span class="project-details__title-area__edit">
|
||||
Edit Description
|
||||
</span>
|
||||
</div>
|
||||
<p class="project-details__description">test</p>
|
||||
</div>
|
||||
`;
|
@ -38,7 +38,7 @@ exports[`Team HeaderArea renders correctly with 1 selected user and delete click
|
||||
<div class="header-after-delete-click"><span class="header-after-delete-click__delete-confirmation">Are you sure you want to delete <b>1</b> user?</span>
|
||||
<div class="header-after-delete-click__button-area">
|
||||
<vbutton-stub label="Delete" width="122px" height="48px" onpress="function () { [native code] }" class="button deletion"></vbutton-stub>
|
||||
<vbutton-stub label="Cancel" width="122px" height="48px" iswhite="true" onpress="function () { [native code] }" class="button"></vbutton-stub>
|
||||
<vbutton-stub label="Cancel" width="122px" height="48px" istransparent="true" onpress="function () { [native code] }" class="button"></vbutton-stub>
|
||||
</div>
|
||||
</div>
|
||||
</vheader-stub>
|
||||
@ -64,7 +64,7 @@ exports[`Team HeaderArea renders correctly with 2 selected users and delete clic
|
||||
<div class="header-after-delete-click"><span class="header-after-delete-click__delete-confirmation">Are you sure you want to delete <b>2</b> users?</span>
|
||||
<div class="header-after-delete-click__button-area">
|
||||
<vbutton-stub label="Delete" width="122px" height="48px" onpress="function () { [native code] }" class="button deletion"></vbutton-stub>
|
||||
<vbutton-stub label="Cancel" width="122px" height="48px" iswhite="true" onpress="function () { [native code] }" class="button"></vbutton-stub>
|
||||
<vbutton-stub label="Cancel" width="122px" height="48px" istransparent="true" onpress="function () { [native code] }" class="button"></vbutton-stub>
|
||||
</div>
|
||||
</div>
|
||||
</vheader-stub>
|
||||
@ -111,7 +111,7 @@ exports[`Team HeaderArea renders correctly with selected users 1`] = `
|
||||
<!---->
|
||||
<div class="header-selected-members">
|
||||
<vbutton-stub label="Delete" width="122px" height="48px" onpress="function () { [native code] }" class="button deletion"></vbutton-stub>
|
||||
<vbutton-stub label="Cancel" width="122px" height="48px" iswhite="true" onpress="function () { [native code] }" class="button"></vbutton-stub> <span class="header-selected-members__info-text"><b>2</b> users selected</span>
|
||||
<vbutton-stub label="Cancel" width="122px" height="48px" istransparent="true" onpress="function () { [native code] }" class="button"></vbutton-stub> <span class="header-selected-members__info-text"><b>2</b> users selected</span>
|
||||
</div>
|
||||
<!---->
|
||||
</vheader-stub>
|
||||
|
@ -5,7 +5,7 @@ import Vuex from 'vuex';
|
||||
|
||||
import { ProjectsApiGql } from '@/api/projects';
|
||||
import { makeProjectsModule, PROJECTS_ACTIONS, PROJECTS_MUTATIONS } from '@/store/modules/projects';
|
||||
import { Project, ProjectLimits, UpdateProjectFields } from '@/types/projects';
|
||||
import { Project, ProjectFields, ProjectLimits } from '@/types/projects';
|
||||
import { createLocalVue } from '@vue/test-utils';
|
||||
|
||||
const Vue = createLocalVue();
|
||||
@ -237,7 +237,7 @@ describe('actions', () => {
|
||||
|
||||
state.projects = projects;
|
||||
const newName = 'newName';
|
||||
const fieldsToUpdate = new UpdateProjectFields(newName, state.projects[0].description);
|
||||
const fieldsToUpdate = new ProjectFields(newName, state.projects[0].description);
|
||||
|
||||
await store.dispatch(UPDATE_NAME, fieldsToUpdate);
|
||||
|
||||
@ -251,7 +251,7 @@ describe('actions', () => {
|
||||
|
||||
state.projects = projects;
|
||||
const newDescription = 'newDescription1';
|
||||
const fieldsToUpdate = new UpdateProjectFields(state.projects[0].name, newDescription);
|
||||
const fieldsToUpdate = new ProjectFields(state.projects[0].name, newDescription);
|
||||
|
||||
await store.dispatch(UPDATE_DESCRIPTION, fieldsToUpdate);
|
||||
|
||||
@ -263,7 +263,7 @@ describe('actions', () => {
|
||||
|
||||
state.projects = projects;
|
||||
const newDescription = 'newDescription2';
|
||||
const fieldsToUpdate = new UpdateProjectFields(state.projects[0].name, newDescription);
|
||||
const fieldsToUpdate = new ProjectFields(state.projects[0].name, newDescription);
|
||||
|
||||
try {
|
||||
await store.dispatch(UPDATE_DESCRIPTION, fieldsToUpdate);
|
||||
|
Loading…
Reference in New Issue
Block a user