Hide UI elements if the user does not have the right to use them (#211)
Hide Team UI elements if the user does not have the rights to use them Fix replacing the right saved in the model when updating Hide UI-Elements on task if the user does not have the rights to use them Hide UI-Elements on gantt if the user does not have the rights to use them Hide UI-Elements on kanban if the user does not have rights to use them Fix canWrite condition Hide list components if the user has no right to write to the list Add max right to model Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/211
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="editor">
|
||||
<div class="tabs is-right" v-if="hasPreview">
|
||||
<div class="editor" :class="{'is-pulled-up': isEditEnabled}">
|
||||
<div class="tabs is-right" v-if="hasPreview && isEditEnabled">
|
||||
<ul>
|
||||
<li :class="{'is-active': isPreviewActive}" v-if="isEditActive">
|
||||
<a @click="showPreview">Preview</a>
|
||||
@ -58,6 +58,9 @@
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isEditEnabled: {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -35,7 +35,7 @@
|
||||
'has-dark-text': colorIsDark(t.hexColor)
|
||||
}"
|
||||
:style="{'border-color': t.hexColor, 'background-color': t.hexColor}"
|
||||
:isActive="true"
|
||||
:isActive="canWrite"
|
||||
:x="t.offsetDays * dayWidth - 6"
|
||||
:y="0"
|
||||
:w="t.durationDays * dayWidth"
|
||||
@ -67,7 +67,7 @@
|
||||
<div class="row" v-for="(t, k) in tasksWithoutDates" :key="t.id" :style="{background: 'repeating-linear-gradient(90deg, #ededed, #ededed 1px, ' + (k % 2 === 0 ? '#fafafa 1px, #fafafa ' : '#fff 1px, #fff ') + dayWidth + 'px)'}">
|
||||
<VueDragResize
|
||||
class="task nodate"
|
||||
:isActive="true"
|
||||
:isActive="canWrite"
|
||||
:x="dayOffsetUntilToday * dayWidth - 6"
|
||||
:y="0"
|
||||
:h="31"
|
||||
@ -88,7 +88,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<form @submit.prevent="addNewTask()" class="add-new-task">
|
||||
<form @submit.prevent="addNewTask()" class="add-new-task" v-if="canWrite">
|
||||
<transition name="width">
|
||||
<input
|
||||
type="text"
|
||||
@ -138,6 +138,8 @@
|
||||
import priorities from '../../models/priorities'
|
||||
import PriorityLabel from './partials/priorityLabel'
|
||||
import TaskCollectionService from '../../services/taskCollection'
|
||||
import {mapState} from 'vuex'
|
||||
import Rights from '../../models/rights.json'
|
||||
|
||||
export default {
|
||||
name: 'GanttChart',
|
||||
@ -201,6 +203,9 @@
|
||||
mounted() {
|
||||
this.buildTheGanttChart()
|
||||
},
|
||||
computed: mapState({
|
||||
canWrite: state => state.currentList.maxRight > Rights.READ,
|
||||
}),
|
||||
methods: {
|
||||
buildTheGanttChart() {
|
||||
this.setDates()
|
||||
|
@ -6,6 +6,7 @@
|
||||
</span>
|
||||
Attachments
|
||||
<a
|
||||
v-if="editEnabled"
|
||||
class="button is-primary is-outlined is-small noshadow"
|
||||
@click="$refs.files.click()"
|
||||
:disabled="attachmentService.loading">
|
||||
@ -14,7 +15,7 @@
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<input type="file" id="files" ref="files" multiple @change="uploadNewAttachment()" :disabled="attachmentService.loading"/>
|
||||
<input type="file" id="files" ref="files" multiple @change="uploadNewAttachment()" :disabled="attachmentService.loading" v-if="editEnabled"/>
|
||||
<progress v-if="attachmentService.uploadProgress > 0" class="progress is-primary" :value="attachmentService.uploadProgress" max="100">{{ attachmentService.uploadProgress }}%</progress>
|
||||
|
||||
<table>
|
||||
@ -41,7 +42,7 @@
|
||||
<icon icon="cloud-download-alt"/>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-danger noshadow" v-tooltip="'Delete this attachment'" @click="() => {attachmentToDelete = a; showDeleteModal = true}">
|
||||
<a v-if="editEnabled" class="button is-danger noshadow" v-tooltip="'Delete this attachment'" @click="() => {attachmentToDelete = a; showDeleteModal = true}">
|
||||
<span class="icon">
|
||||
<icon icon="trash-alt"/>
|
||||
</span>
|
||||
@ -52,7 +53,7 @@
|
||||
</table>
|
||||
|
||||
<!-- Dropzone -->
|
||||
<div class="dropzone" :class="{ 'hidden': !showDropzone }">
|
||||
<div class="dropzone" :class="{ 'hidden': !showDropzone }" v-if="editEnabled">
|
||||
<div class="drop-hint">
|
||||
<div class="icon">
|
||||
<icon icon="cloud-upload-alt"/>
|
||||
@ -102,7 +103,10 @@
|
||||
},
|
||||
initialAttachments: {
|
||||
type: Array,
|
||||
}
|
||||
},
|
||||
editEnabled: {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.attachmentService = new AttachmentService()
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="content details has-top-border">
|
||||
<h1>
|
||||
<div class="content details" :class="{'has-top-border': canWrite || comments.length > 0}">
|
||||
<h1 v-if="canWrite || comments.length > 0">
|
||||
<span class="icon is-grey">
|
||||
<icon :icon="['far', 'comments']"/>
|
||||
</span>
|
||||
@ -15,7 +15,7 @@
|
||||
<img class="image is-avatar" :src="c.author.getAvatarUrl(48)" alt="" width="48" height="48"/>
|
||||
</figure>
|
||||
<div class="media-content">
|
||||
<div class="comment-info">
|
||||
<div class="comment-info" :class="{'is-pulled-up': canWrite}">
|
||||
<strong>{{ c.author.username }}</strong>
|
||||
<small v-tooltip="formatDate(c.created)">{{ formatDateSince(c.created) }}</small>
|
||||
<small v-if="+new Date(c.created) !== +new Date(c.updated)" v-tooltip="formatDate(c.updated)"> ·
|
||||
@ -27,13 +27,14 @@
|
||||
@change="() => {toggleEdit(c);editComment()}"
|
||||
:upload-enabled="true"
|
||||
:upload-callback="attachmentUpload"
|
||||
:is-edit-enabled="canWrite"
|
||||
/>
|
||||
<div class="comment-actions">
|
||||
<div class="comment-actions" v-if="canWrite">
|
||||
<a @click="toggleDelete(c.id)">Remove</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="media comment">
|
||||
<div class="media comment" v-if="canWrite">
|
||||
<figure class="media-left">
|
||||
<img class="image is-avatar" :src="userAvatar" alt="" width="48" height="48"/>
|
||||
</figure>
|
||||
@ -95,7 +96,10 @@
|
||||
taskId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
canWrite: {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -125,7 +129,7 @@
|
||||
watch: {
|
||||
taskId() {
|
||||
this.loadComments()
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
userAvatar() {
|
||||
|
@ -17,10 +17,11 @@
|
||||
track-by="id"
|
||||
select-label="Assign this user"
|
||||
:showNoOptions="false"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<template slot="tag" slot-scope="{ option }">
|
||||
<user :user="option" :show-username="false" :avatar-size="30"/>
|
||||
<a @click="removeAssignee(option)" class="remove-assignee">
|
||||
<a @click="removeAssignee(option)" class="remove-assignee" v-if="!disabled">
|
||||
<icon icon="times"/>
|
||||
</a>
|
||||
</template>
|
||||
@ -65,7 +66,10 @@
|
||||
initialAssignees: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -19,6 +19,7 @@
|
||||
:showNoOptions="false"
|
||||
@tag="createAndAddLabel"
|
||||
tag-placeholder="Add this as new label"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<template slot="tag" slot-scope="{ option }">
|
||||
<span class="tag"
|
||||
@ -54,6 +55,9 @@
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="select">
|
||||
<select v-model.number="percentDone" @change="updateData">
|
||||
<select v-model.number="percentDone" @change="updateData" :disabled="disabled">
|
||||
<option value="0">0%</option>
|
||||
<option value="0.1">10%</option>
|
||||
<option value="0.2">20%</option>
|
||||
@ -28,7 +28,10 @@
|
||||
value: {
|
||||
default: 0,
|
||||
type: Number,
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// Set the priority to the :value every time it changes from the outside
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="select">
|
||||
<select v-model="priority" @change="updateData">
|
||||
<select v-model="priority" @change="updateData" :disabled="disabled">
|
||||
<option :value="priorities.UNSET">Unset</option>
|
||||
<option :value="priorities.LOW">Low</option>
|
||||
<option :value="priorities.MEDIUM">Medium</option>
|
||||
@ -26,7 +26,10 @@
|
||||
value: {
|
||||
default: 0,
|
||||
type: Number,
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// Set the priority to the :value every time it changes from the outside
|
||||
|
@ -1,47 +1,49 @@
|
||||
<template>
|
||||
<div class="task-relations">
|
||||
<label class="label">New Task Relation</label>
|
||||
<div class="field">
|
||||
<multiselect
|
||||
v-model="newTaskRelationTask"
|
||||
:options="foundTasks"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:loading="taskService.loading"
|
||||
:internal-search="true"
|
||||
@search-change="findTasks"
|
||||
placeholder="Type search for a new task to add as related..."
|
||||
label="title"
|
||||
track-by="id"
|
||||
:taggable="true"
|
||||
:showNoOptions="false"
|
||||
@tag="createAndRelateTask"
|
||||
tag-placeholder="Add this as new related task"
|
||||
>
|
||||
<template slot="clear" slot-scope="props">
|
||||
<div
|
||||
class="multiselect__clear"
|
||||
v-if="newTaskRelationTask !== null && newTaskRelationTask.id !== 0"
|
||||
@mousedown.prevent.stop="clearAllFoundTasks(props.search)"></div>
|
||||
</template>
|
||||
<span slot="noResult">No task found. Consider changing the search query.</span>
|
||||
</multiselect>
|
||||
</div>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<div class="select is-fullwidth has-defaults">
|
||||
<select v-model="newTaskRelationKind">
|
||||
<option value="unset">Select a relation kind</option>
|
||||
<option v-for="(label, rk) in relationKinds" :key="rk" :value="rk">
|
||||
{{ label[0] }}
|
||||
</option>
|
||||
</select>
|
||||
<template v-if="editEnabled">
|
||||
<label class="label">New Task Relation</label>
|
||||
<div class="field">
|
||||
<multiselect
|
||||
v-model="newTaskRelationTask"
|
||||
:options="foundTasks"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:loading="taskService.loading"
|
||||
:internal-search="true"
|
||||
@search-change="findTasks"
|
||||
placeholder="Type search for a new task to add as related..."
|
||||
label="title"
|
||||
track-by="id"
|
||||
:taggable="true"
|
||||
:showNoOptions="false"
|
||||
@tag="createAndRelateTask"
|
||||
tag-placeholder="Add this as new related task"
|
||||
>
|
||||
<template slot="clear" slot-scope="props">
|
||||
<div
|
||||
class="multiselect__clear"
|
||||
v-if="newTaskRelationTask !== null && newTaskRelationTask.id !== 0"
|
||||
@mousedown.prevent.stop="clearAllFoundTasks(props.search)"></div>
|
||||
</template>
|
||||
<span slot="noResult">No task found. Consider changing the search query.</span>
|
||||
</multiselect>
|
||||
</div>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<div class="select is-fullwidth has-defaults">
|
||||
<select v-model="newTaskRelationKind">
|
||||
<option value="unset">Select a relation kind</option>
|
||||
<option v-for="(label, rk) in relationKinds" :key="rk" :value="rk">
|
||||
{{ label[0] }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-primary" @click="addTaskRelation()">Add task Relation</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-primary" @click="addTaskRelation()">Add task Relation</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="related-tasks" v-for="(rts, kind ) in relatedTasks" :key="kind">
|
||||
<template v-if="rts.length > 0">
|
||||
@ -50,13 +52,17 @@
|
||||
<div class="task" v-for="t in rts" :key="t.id">
|
||||
<router-link :to="{ name: $route.name, params: { id: t.id } }">
|
||||
<span class="tasktext" :class="{ 'done': t.done}">
|
||||
<span v-if="t.listId !== listId" class="different-list" v-tooltip="'This task belongs to a different list.'">
|
||||
<span
|
||||
v-if="t.listId !== listId"
|
||||
class="different-list"
|
||||
v-tooltip="'This task belongs to a different list.'">
|
||||
{{ $store.getters['lists/getListById'](t.listId) === null ? '' : $store.getters['lists/getListById'](t.listId).title }} >
|
||||
</span>
|
||||
{{t.title}}
|
||||
</span>
|
||||
</router-link>
|
||||
<a
|
||||
v-if="editEnabled"
|
||||
class="remove"
|
||||
@click="() => {showDeleteModal = true; relationToDelete = {relationKind: kind, otherTaskId: t.id}}">
|
||||
<icon icon="trash-alt"/>
|
||||
@ -131,7 +137,10 @@
|
||||
listId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
}
|
||||
},
|
||||
editEnabled: {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.taskService = new TaskService()
|
||||
@ -222,7 +231,7 @@
|
||||
return relationKinds[kind][1]
|
||||
}
|
||||
return relationKinds[kind][0]
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -10,9 +10,10 @@
|
||||
:value="r"
|
||||
:data-index="index"
|
||||
placeholder="Add a new reminder..."
|
||||
:disabled="disabled"
|
||||
>
|
||||
</flat-pickr>
|
||||
<a v-if="index !== (reminders.length - 1)" @click="removeReminderByIndex(index)">
|
||||
<a v-if="index !== (reminders.length - 1) && !disabled" @click="removeReminderByIndex(index)">
|
||||
<icon icon="times"></icon>
|
||||
</a>
|
||||
</div>
|
||||
@ -44,7 +45,10 @@
|
||||
value: {
|
||||
default: () => [],
|
||||
type: Array,
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
flatPickr,
|
||||
|
@ -6,6 +6,7 @@
|
||||
<div class="column is-7 field has-addons">
|
||||
<div class="control">
|
||||
<input
|
||||
:disabled="disabled"
|
||||
class="input"
|
||||
placeholder="Specify an amount..."
|
||||
v-model="repeatAfter.amount"
|
||||
@ -13,7 +14,7 @@
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select v-model="repeatAfter.type" @change="updateData">
|
||||
<select v-model="repeatAfter.type" @change="updateData" :disabled="disabled">
|
||||
<option value="hours">Hours</option>
|
||||
<option value="days">Days</option>
|
||||
<option value="weeks">Weeks</option>
|
||||
@ -24,6 +25,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<fancycheckbox
|
||||
:disabled="disabled"
|
||||
class="column"
|
||||
@change="updateData"
|
||||
v-model="task.repeatFromCurrentDate"
|
||||
@ -54,7 +56,10 @@
|
||||
default: () => {
|
||||
},
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<span>
|
||||
<fancycheckbox v-model="task.done" @change="markAsDone" :disabled="isArchived"/>
|
||||
<fancycheckbox v-model="task.done" @change="markAsDone" :disabled="isArchived || disabled"/>
|
||||
<span class="tasktext" :class="{ 'done': task.done}">
|
||||
<router-link :to="{ name: taskDetailRoute, params: { id: task.id } }">
|
||||
<router-link
|
||||
@ -88,6 +88,10 @@
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
theTask(newVal) {
|
||||
|
Reference in New Issue
Block a user