@@ -31,11 +31,11 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<div class="tab members" :class="{active: 'members'}">
|
<div class="tab members" :class="{active: activeTab === 'members'}" @click="activeTab = 'members'">
|
||||||
<i class="icon-user icon" />
|
<i class="icon-user icon" />
|
||||||
Members
|
Members
|
||||||
</div>
|
</div>
|
||||||
<div class="tab tags">
|
<div class="tab tags" :class="{active: activeTab === 'tags'}" @click="activeTab = 'tags'">
|
||||||
<i class="icon icon-tag" />
|
<i class="icon icon-tag" />
|
||||||
Tags
|
Tags
|
||||||
</div>
|
</div>
|
||||||
@@ -53,7 +53,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<MembersTab :card="currentCard" />
|
<div class="content-tabs">
|
||||||
|
<MembersTab :card="currentCard" @click="activeTab = 'members'" @active-tab="changeActiveTab" />
|
||||||
|
<TagsTab :card="currentCard" @click="activeTab = 'tags'" @active-tab="changeActiveTab" />
|
||||||
|
</div>
|
||||||
<Description :key="currentCard.id" :card="currentCard" @change="descriptionChanged" />
|
<Description :key="currentCard.id" :card="currentCard" @change="descriptionChanged" />
|
||||||
</div>
|
</div>
|
||||||
<div class="activities">
|
<div class="activities">
|
||||||
@@ -84,13 +87,14 @@ import relativeDate from '../../mixins/relativeDate'
|
|||||||
import { showError } from '@nextcloud/dialogs'
|
import { showError } from '@nextcloud/dialogs'
|
||||||
import { getCurrentUser } from '@nextcloud/auth'
|
import { getCurrentUser } from '@nextcloud/auth'
|
||||||
import MembersTab from './MembersTab.vue'
|
import MembersTab from './MembersTab.vue'
|
||||||
|
import TagsTab from './TagsTab.vue'
|
||||||
import Description from './Description.vue'
|
import Description from './Description.vue'
|
||||||
|
|
||||||
const capabilities = window.OC.getCapabilities()
|
const capabilities = window.OC.getCapabilities()
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CardModal',
|
name: 'CardModal',
|
||||||
components: { Avatar, MembersTab, Description },
|
components: { Avatar, MembersTab, Description, TagsTab },
|
||||||
mixins: [relativeDate],
|
mixins: [relativeDate],
|
||||||
props: {
|
props: {
|
||||||
id: {
|
id: {
|
||||||
@@ -179,14 +183,25 @@ export default {
|
|||||||
showModal() {
|
showModal() {
|
||||||
this.$store.dispatch('setConfig', { cardDetailsInModal: true })
|
this.$store.dispatch('setConfig', { cardDetailsInModal: true })
|
||||||
},
|
},
|
||||||
|
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.$store.dispatch('setConfig', { cardDetailsInModal: false })
|
this.$store.dispatch('setConfig', { cardDetailsInModal: false })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
changeActiveTab(tab) {
|
||||||
|
this.activeTab = tab
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.content-tabs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.icon-flash-black {
|
.icon-flash-black {
|
||||||
background-image: url(../../../img/flash-black.svg);
|
background-image: url(../../../img/flash-black.svg);
|
||||||
width: 15px;
|
width: 15px;
|
||||||
|
|||||||
@@ -32,16 +32,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="members" @click="showSelelectMembers = true">
|
<div class="members">
|
||||||
<Avatar v-for="option in assignedUsers"
|
<Avatar v-for="option in assignedUsers"
|
||||||
:key="option.primaryKey"
|
:key="option.primaryKey"
|
||||||
:user="option.uid"
|
:user="option.uid"
|
||||||
:display-name="option.displayname"
|
:display-name="option.displayname"
|
||||||
:is-no-user="option.isNoUser"
|
:is-no-user="option.isNoUser"
|
||||||
:size="32" />
|
:size="32" />
|
||||||
<div class="button new select-member-btn">
|
<div class="button new select-member-btn" @click="selectMembers">
|
||||||
<span class="icon icon-add" />
|
<span class="icon icon-add" />
|
||||||
<span class="hidden-visually"></span>
|
<span class="hidden-visually" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -109,6 +109,10 @@ export default {
|
|||||||
this.initialize()
|
this.initialize()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
selectMembers() {
|
||||||
|
this.showSelelectMembers = true
|
||||||
|
this.$emit('active-tab', 'members')
|
||||||
|
},
|
||||||
removeUserFromCard(user) {
|
removeUserFromCard(user) {
|
||||||
this.$store.dispatch('removeUserFromCard', {
|
this.$store.dispatch('removeUserFromCard', {
|
||||||
card: this.copiedCard,
|
card: this.copiedCard,
|
||||||
|
|||||||
140
src/components/card/TagsTab.vue
Normal file
140
src/components/card/TagsTab.vue
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<div class="section-details">
|
||||||
|
<div v-if="showSelelectTags" @mouseleave="showSelelectTags = false">
|
||||||
|
<Multiselect v-model="assignedLabels"
|
||||||
|
:multiple="true"
|
||||||
|
:disabled="!canEdit"
|
||||||
|
:options="labelsSorted"
|
||||||
|
:placeholder="t('deck', 'Assign a tag to this card…')"
|
||||||
|
:taggable="true"
|
||||||
|
label="title"
|
||||||
|
track-by="id"
|
||||||
|
@select="addLabelToCard"
|
||||||
|
@remove="removeLabelFromCard">
|
||||||
|
<template #option="scope">
|
||||||
|
<div :style="{ backgroundColor: '#' + scope.option.color, color: textColor(scope.option.color)}" class="tag">
|
||||||
|
{{ scope.option.title }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #tag="scope">
|
||||||
|
<div :style="{ backgroundColor: '#' + scope.option.color, color: textColor(scope.option.color)}" class="tag">
|
||||||
|
{{ scope.option.title }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Multiselect>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="card.labels.length > 0" class="labels">
|
||||||
|
<div v-for="label in card.labels"
|
||||||
|
:key="label.id"
|
||||||
|
:style="labelStyle(label)"
|
||||||
|
class="labels-item">
|
||||||
|
<span @click.stop="applyLabelFilter(label)">{{ label.title }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="button new select-tag" @click="add">
|
||||||
|
<span class="icon icon-add" />
|
||||||
|
<span class="hidden-visually" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { Multiselect } from '@nextcloud/vue'
|
||||||
|
import { mapState, mapGetters } from 'vuex'
|
||||||
|
import Color from '../../mixins/color'
|
||||||
|
import labelStyle from '../../mixins/labelStyle'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { Multiselect },
|
||||||
|
mixins: [Color, labelStyle],
|
||||||
|
props: {
|
||||||
|
card: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
assignedLabels: null,
|
||||||
|
showSelelectTags: false,
|
||||||
|
copiedCard: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
currentBoard: state => state.currentBoard,
|
||||||
|
}),
|
||||||
|
...mapGetters(['canEdit']),
|
||||||
|
labelsSorted() {
|
||||||
|
return [...this.currentBoard.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initialize()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
add() {
|
||||||
|
this.showSelelectTags = true
|
||||||
|
this.$emit('active-tab', 'tags')
|
||||||
|
},
|
||||||
|
async initialize() {
|
||||||
|
if (!this.card) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.copiedCard = JSON.parse(JSON.stringify(this.card))
|
||||||
|
this.assignedLabels = [...this.card.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
|
||||||
|
},
|
||||||
|
openCard() {
|
||||||
|
const boardId = this.card && this.card.boardId ? this.card.boardId : this.$route.params.id
|
||||||
|
this.$router.push({ name: 'card', params: { id: boardId, cardId: this.card.id } }).catch(() => {})
|
||||||
|
},
|
||||||
|
addLabelToCard(newLabel) {
|
||||||
|
this.copiedCard.labels.push(newLabel)
|
||||||
|
const data = {
|
||||||
|
card: this.copiedCard,
|
||||||
|
labelId: newLabel.id,
|
||||||
|
}
|
||||||
|
this.$store.dispatch('addLabel', data)
|
||||||
|
},
|
||||||
|
removeLabelFromCard(removedLabel) {
|
||||||
|
|
||||||
|
const removeIndex = this.copiedCard.labels.findIndex((label) => {
|
||||||
|
return label.id === removedLabel.id
|
||||||
|
})
|
||||||
|
if (removeIndex !== -1) {
|
||||||
|
this.copiedCard.labels.splice(removeIndex, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
card: this.copiedCard,
|
||||||
|
labelId: removedLabel.id,
|
||||||
|
}
|
||||||
|
this.$store.dispatch('removeLabel', data)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.labels {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
min-width: 110px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-tag {
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
padding: 5px 7px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -135,7 +135,7 @@ export default {
|
|||||||
},
|
},
|
||||||
activeBoards() {
|
activeBoards() {
|
||||||
return this.$store.getters.boards.filter((item) => item.deletedAt === 0 && item.archived === false)
|
return this.$store.getters.boards.filter((item) => item.deletedAt === 0 && item.archived === false)
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openCard() {
|
openCard() {
|
||||||
|
|||||||
Reference in New Issue
Block a user