General design enhancements and scroll area changes
Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
@@ -45,7 +45,7 @@ export default {
|
||||
},
|
||||
parseMessage(activity) {
|
||||
const subject = activity.subject_rich[0]
|
||||
const parameters = activity.subject_rich[1]
|
||||
const parameters = JSON.parse(JSON.stringify(activity.subject_rich[1]))
|
||||
if (parameters.after && typeof parameters.after.id === 'string' && parameters.after.id.startsWith('dt:')) {
|
||||
const dateTime = parameters.after.id.substr(3)
|
||||
parameters.after.name = window.moment(dateTime).format('L LTS')
|
||||
|
||||
@@ -23,21 +23,16 @@
|
||||
<template>
|
||||
<div class="controls">
|
||||
<div id="app-navigation-toggle-custom" class="icon-menu" @click="toggleNav" />
|
||||
<div class="breadcrumb">
|
||||
<div class="crumb svg last">
|
||||
<router-link to="/boards" class="icon-home" title="All Boards">
|
||||
<span class="hidden-visually">All Boards</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="board" class="crumb svg">
|
||||
<div v-if="board" class="board-title">
|
||||
<div :style="{backgroundColor: '#' + board.color}" class="board-bullet" />
|
||||
<a href="#">{{ board.title }}</a>
|
||||
<router-link :to="{name: 'board.details'}" class="icon-shared" />
|
||||
<h2><a href="#">{{ board.title }}</a></h2>
|
||||
</div>
|
||||
<div v-if="board" class="board-actions">
|
||||
<div id="stack-add">
|
||||
<form @submit.prevent="clickAddNewStack()">
|
||||
<div id="stack-add" v-click-outside="hideAddStack">
|
||||
<Actions v-if="!isAddStackVisible">
|
||||
<ActionButton icon="icon-add" :title="t('deck', 'Add new stack')" @click.stop="showAddStack" />
|
||||
</Actions>
|
||||
<form v-else @submit.prevent="addNewStack()">
|
||||
<label for="new-stack-input-main" class="hidden-visually">Add a new stack</label>
|
||||
<input id="new-stack-input-main"
|
||||
v-model="newStackTitle"
|
||||
@@ -45,25 +40,37 @@
|
||||
class="no-close"
|
||||
placeholder="Add a new stack"
|
||||
required>
|
||||
<input v-tooltip="t('deck', 'clickAddNewStack')"
|
||||
<input v-tooltip="t('deck', 'Add new stack')"
|
||||
class="icon-confirm"
|
||||
type="submit"
|
||||
value="">
|
||||
</form>
|
||||
</div>
|
||||
<div class="board-action-buttons">
|
||||
<button :style="archivStyle"
|
||||
title="Show archived cards"
|
||||
class="icon icon-archive"
|
||||
@click="toggleShowArchived" />
|
||||
<button :class="[(compactMode ? 'icon-toggle-compact-collapsed' : 'icon-toggle-compact-expanded')]"
|
||||
title="Toggle compact mode"
|
||||
class="icon"
|
||||
@click="toggleCompactMode" />
|
||||
<router-link v-tooltip="t('deck', 'Board settings')"
|
||||
:to="{name: 'board.details'}"
|
||||
class="icon-settings-dark"
|
||||
tag="button" />
|
||||
<Actions style="opacity: .5;">
|
||||
<ActionButton v-if="showArchived"
|
||||
icon="icon-archive"
|
||||
:title="t('deck', 'Show archived cards')"
|
||||
@click="toggleShowArchived" />
|
||||
<ActionButton v-else
|
||||
icon="icon-archive"
|
||||
:title="t('deck', 'Hide archived cards')"
|
||||
@click="toggleShowArchived" />
|
||||
</Actions>
|
||||
<Actions>
|
||||
<ActionButton v-if="compactMode"
|
||||
icon="icon-toggle-compact-collapsed"
|
||||
:title="t('deck', 'Toggle compact mode')"
|
||||
@click="toggleCompactMode" />
|
||||
<ActionButton v-else
|
||||
icon="icon-toggle-compact-expanded"
|
||||
:title="t('deck', 'Toggle compact mode')"
|
||||
@click="toggleCompactMode" />
|
||||
</Actions>
|
||||
<!-- FIXME: ActionRouter currently doesn't work as an inline action -->
|
||||
<Actions>
|
||||
<ActionButton icon="icon-share" @click="toggleDetailsView" />
|
||||
</Actions>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,8 +78,13 @@
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { Actions, ActionButton } from '@nextcloud/vue'
|
||||
|
||||
export default {
|
||||
name: 'Controls',
|
||||
components: {
|
||||
Actions, ActionButton,
|
||||
},
|
||||
props: {
|
||||
board: {
|
||||
type: Object,
|
||||
@@ -85,27 +97,23 @@ export default {
|
||||
newStackTitle: '',
|
||||
stack: '',
|
||||
showArchived: false,
|
||||
isAddStackVisible: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
compactMode: state => state.compactMode,
|
||||
}),
|
||||
archivStyle() {
|
||||
|
||||
if (this.showArchived === true) {
|
||||
return 'opacity: 1.0'
|
||||
detailsRoute() {
|
||||
return {
|
||||
name: 'board.details',
|
||||
}
|
||||
return 'opacity: 0.3'
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleNav() {
|
||||
this.$store.dispatch('toggleNav')
|
||||
},
|
||||
toggleSidebar: function() {
|
||||
this.$store.dispatch('toggleSidebar')
|
||||
},
|
||||
toggleCompactMode() {
|
||||
this.$store.dispatch('toggleCompactMode')
|
||||
},
|
||||
@@ -113,11 +121,25 @@ export default {
|
||||
this.$store.dispatch('toggleShowArchived')
|
||||
this.showArchived = !this.showArchived
|
||||
},
|
||||
clickAddNewStack() {
|
||||
addNewStack() {
|
||||
this.stack = { title: this.newStackTitle }
|
||||
this.$store.dispatch('createStack', this.stack)
|
||||
this.newStackTitle = ''
|
||||
this.stack = null
|
||||
this.isAddStackVisible = false
|
||||
},
|
||||
showAddStack() {
|
||||
this.isAddStackVisible = true
|
||||
},
|
||||
hideAddStack() {
|
||||
this.isAddStackVisible = false
|
||||
},
|
||||
toggleDetailsView() {
|
||||
if (this.$route.name === 'board.details') {
|
||||
this.$router.push({ name: 'board' })
|
||||
} else {
|
||||
this.$router.push({ name: 'board.details' })
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -125,17 +147,13 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.controls {
|
||||
.crumb {
|
||||
order: 0;
|
||||
.board-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
a:nth-child(2),
|
||||
a:nth-child(3) {
|
||||
padding-left: 0;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
a .icon {
|
||||
margin-top: 2px;
|
||||
h2 {
|
||||
margin: 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.board-bullet {
|
||||
@@ -181,17 +199,11 @@ export default {
|
||||
|
||||
.board-action-buttons {
|
||||
display: flex;
|
||||
padding: 3px 4px 7px 4px;
|
||||
button {
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
width: 44px;
|
||||
margin: 0 0 0 -1px;
|
||||
}
|
||||
button:first-child {
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
button:last-child {
|
||||
border-radius: 0 3px 3px 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,11 +21,16 @@
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="board-wrapper">
|
||||
<Controls :board="board" />
|
||||
<div v-if="board" class="board">
|
||||
<div v-if="loading" class="emptycontent">
|
||||
<div class="icon icon-loading" />
|
||||
<h2>{{ t('deck', 'Loading board') }}</h2>
|
||||
<p />
|
||||
</div>
|
||||
<div v-else-if="board" class="board">
|
||||
<Container lock-axix="y" orientation="horizontal" @drop="onDropStack">
|
||||
<Draggable v-for="stack in stacksByBoard" :key="stack.id" class="stack">
|
||||
<Draggable v-for="stack in stacksByBoard" :key="stack.id">
|
||||
<Stack :stack="stack" />
|
||||
</Draggable>
|
||||
</Container>
|
||||
@@ -75,9 +80,6 @@ export default {
|
||||
stacksByBoard() {
|
||||
return this.$store.getters.stacksByBoard(this.board.id)
|
||||
},
|
||||
/* cardsByStack() {
|
||||
return (id) => this.$store.getters.cardsByStack(id)
|
||||
} */
|
||||
},
|
||||
watch: {
|
||||
id: 'fetchData',
|
||||
@@ -89,34 +91,17 @@ export default {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData() {
|
||||
|
||||
this.$store.dispatch('loadBoardById', this.id).then(response => {
|
||||
this.$store.dispatch('loadStacks', this.id).then(response => {
|
||||
this.loading = false
|
||||
})
|
||||
})
|
||||
|
||||
/* this.boardApi.loadById(this.id)
|
||||
.then((board) => {
|
||||
this.$store.dispatch('setCurrentBoard', board)
|
||||
this.$store.dispatch('loadStacks', board)
|
||||
this.$store.dispatch('setAssignableUsers', board.users)
|
||||
this.loading = false
|
||||
this.$store.state.labels = board.labels
|
||||
}) */
|
||||
async fetchData() {
|
||||
this.loading = true
|
||||
await this.$store.dispatch('loadBoardById', this.id)
|
||||
await this.$store.dispatch('loadStacks', this.id)
|
||||
this.loading = false
|
||||
},
|
||||
|
||||
onDropStack({ removedIndex, addedIndex }) {
|
||||
this.$store.dispatch('orderStack', { stack: this.stacksByBoard[removedIndex], removedIndex, addedIndex })
|
||||
},
|
||||
/* onDropCard({ removedIndex, addedIndex }) {
|
||||
|
||||
}, */
|
||||
/* payloadForCard(stackId) {
|
||||
return index => {
|
||||
return this.cardsByStack(stackId)[index]
|
||||
}
|
||||
}, */
|
||||
createStack() {
|
||||
const newStack = {
|
||||
title: 'FooBar',
|
||||
@@ -125,9 +110,6 @@ export default {
|
||||
}
|
||||
this.$store.dispatch('createStack', newStack)
|
||||
},
|
||||
/* deleteStack(stack) {
|
||||
this.$store.dispatch('deleteStack', stack)
|
||||
} */
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -138,28 +120,61 @@ export default {
|
||||
$stack-spacing: 10px;
|
||||
$stack-width: 300px;
|
||||
|
||||
.board-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: calc(100vh - 50px);
|
||||
}
|
||||
|
||||
.board {
|
||||
margin-left: $board-spacing;
|
||||
position: relative;
|
||||
height: calc(100% - 44px);
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
.stack {
|
||||
width: $stack-width;
|
||||
padding: $stack-spacing;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
.smooth-dnd-container.vertical {
|
||||
/**
|
||||
* Combined rules to handle proper board scrolling and
|
||||
* drag and drop behavior
|
||||
*/
|
||||
.smooth-dnd-container.horizontal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
align-items: stretch;
|
||||
.smooth-dnd-draggable-wrapper::v-deep {
|
||||
display: flex;
|
||||
height: auto;
|
||||
|
||||
.smooth-dnd-container.vertical > .smooth-dnd-draggable-wrapper {
|
||||
overflow: initial;
|
||||
}
|
||||
.stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.smooth-dnd-container.vertical .smooth-dnd-draggable-wrapper {
|
||||
height: auto;
|
||||
} */
|
||||
.smooth-dnd-container.vertical {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 3px;
|
||||
padding-top: 0;
|
||||
/**
|
||||
* Use this to scroll each stack individually
|
||||
* This currenly has the issue that the popover menu will be cut off
|
||||
*/
|
||||
/*
|
||||
overflow-x: scroll;
|
||||
height: calc(100vh - 50px - 44px * 2 - 30px);
|
||||
max-height: calc(100vh - 50px - 44px * 2 - 30px);
|
||||
*/
|
||||
}
|
||||
|
||||
.smooth-dnd-container.vertical > .smooth-dnd-draggable-wrapper {
|
||||
overflow: initial;
|
||||
}
|
||||
|
||||
.smooth-dnd-container.vertical .smooth-dnd-draggable-wrapper {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<div>
|
||||
<Multiselect
|
||||
v-model="addAcl"
|
||||
:placeholder="t('deck', 'Share board with a user, group or circle …')"
|
||||
:options="formatedSharees"
|
||||
:user-select="true"
|
||||
label="displayName"
|
||||
|
||||
@@ -36,23 +36,23 @@
|
||||
value="">
|
||||
</form>
|
||||
</transition>
|
||||
<Actions>
|
||||
<Actions :force-menu="true">
|
||||
<ActionButton icon="icon-delete" @click="deleteStack(stack)">
|
||||
{{ t('deck', 'Delete stack') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
<Actions>
|
||||
<ActionButton icon="icon-add" @click="showAddCard=true">
|
||||
{{ t('deck', 'Add card') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
</div>
|
||||
|
||||
<Container :get-child-payload="payloadForCard(stack.id)" group-name="stack" @drop="($event) => onDropCard(stack.id, $event)">
|
||||
<Draggable v-for="card in cardsByStack(stack.id)" :key="card.id">
|
||||
<CardItem v-if="card" :id="card.id" />
|
||||
</Draggable>
|
||||
</Container>
|
||||
|
||||
<form class="stack--card-add" @submit.prevent="clickAddCard()">
|
||||
<form v-if="showAddCard" class="stack--card-add" @submit.prevent="clickAddCard()">
|
||||
<label for="new-stack-input-main" class="hidden-visually">Add a new card</label>
|
||||
<input id="new-stack-input-main"
|
||||
v-model="newCardTitle"
|
||||
v-focus
|
||||
type="text"
|
||||
class="no-close"
|
||||
placeholder="Add a new card"
|
||||
@@ -62,6 +62,12 @@
|
||||
type="submit"
|
||||
value="">
|
||||
</form>
|
||||
|
||||
<Container :get-child-payload="payloadForCard(stack.id)" group-name="stack" @drop="($event) => onDropCard(stack.id, $event)">
|
||||
<Draggable v-for="card in cardsByStack(stack.id)" :key="card.id">
|
||||
<CardItem v-if="card" :id="card.id" />
|
||||
</Draggable>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -93,6 +99,7 @@ export default {
|
||||
editing: false,
|
||||
copiedStack: '',
|
||||
newCardTitle: '',
|
||||
showAddCard: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -147,6 +154,7 @@ export default {
|
||||
}
|
||||
this.$store.dispatch('addCard', newCard)
|
||||
this.newCardTitle = ''
|
||||
this.showAddCard = false
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -155,29 +163,24 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
|
||||
$stack-spacing: 10px;
|
||||
$stack-width: 300px;
|
||||
$stack-width: 260px;
|
||||
|
||||
.stack {
|
||||
width: $stack-width;
|
||||
padding: $stack-spacing;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.smooth-dnd-container.vertical {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.smooth-dnd-container.vertical > .smooth-dnd-draggable-wrapper {
|
||||
overflow: initial;
|
||||
}
|
||||
|
||||
.smooth-dnd-container.vertical .smooth-dnd-draggable-wrapper {
|
||||
height: auto;
|
||||
margin-left: $stack-spacing;
|
||||
margin-right: $stack-spacing;
|
||||
}
|
||||
|
||||
.stack--header {
|
||||
display: flex;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
padding: 3px;
|
||||
margin: 3px -3px;
|
||||
margin-right: -10px;
|
||||
margin-top: 0;
|
||||
background-color: var(--color-main-background-translucent);
|
||||
|
||||
h3, form {
|
||||
flex-grow: 1;
|
||||
@@ -191,10 +194,23 @@ export default {
|
||||
|
||||
.stack--card-add {
|
||||
display: flex;
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
box-shadow: 0 0 3px #aaa;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 15px;
|
||||
|
||||
input[type=text] {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rules to handle scrolling behaviour are inherited from Board.vue
|
||||
*/
|
||||
|
||||
</style>
|
||||
|
||||
@@ -133,6 +133,7 @@ export default {
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.labels li {
|
||||
display: flex;
|
||||
margin-bottom: 3px;
|
||||
|
||||
.label-title {
|
||||
@@ -144,14 +145,21 @@ export default {
|
||||
}
|
||||
}
|
||||
&:not(.editing) button {
|
||||
width: 40px;
|
||||
height: 34px;
|
||||
width: 44px;
|
||||
margin: 0;
|
||||
margin-left: -3px;
|
||||
}
|
||||
}
|
||||
.labels li {
|
||||
display: flex;
|
||||
|
||||
.color-picker {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.color-picker-button {
|
||||
width: 100px;
|
||||
margin-left: 20px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
&.editing {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<Multiselect v-model="allLabels"
|
||||
:multiple="true"
|
||||
:options="currentBoard.labels"
|
||||
:placeholder="t('deck', 'Assign a tag to this card…')"
|
||||
:taggable="true"
|
||||
label="title"
|
||||
track-by="id"
|
||||
@@ -63,6 +64,7 @@
|
||||
<Multiselect v-model="assignedUsers"
|
||||
:multiple="true"
|
||||
:options="assignableUsers"
|
||||
:placeholder="t('deck', 'Assign a user to this card…')"
|
||||
label="displayname"
|
||||
track-by="primaryKey"
|
||||
@select="assignUserToCard"
|
||||
@@ -81,6 +83,7 @@
|
||||
</div>
|
||||
<div class="section-details">
|
||||
<DatetimePicker v-model="copiedCard.duedate"
|
||||
:placeholder="t('deck', 'Set a due date')"
|
||||
type="datetime"
|
||||
lang="en"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
@@ -221,6 +224,9 @@ export default {
|
||||
'currentCard': {
|
||||
immediate: true,
|
||||
handler() {
|
||||
if (!this.currentCard) {
|
||||
return
|
||||
}
|
||||
this.copiedCard = JSON.parse(JSON.stringify(this.currentCard))
|
||||
this.allLabels = this.currentCard.labels
|
||||
|
||||
@@ -353,7 +359,8 @@ export default {
|
||||
|
||||
.section-label {
|
||||
background-position: 0px center;
|
||||
width:28px;
|
||||
width: 28px;
|
||||
margin-left: 9px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,9 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
firstUsers() {
|
||||
if (!this.users || this.users.length === 0) {
|
||||
return []
|
||||
}
|
||||
return this.users.slice(0, 3)
|
||||
},
|
||||
avatarUrl() {
|
||||
@@ -79,6 +82,9 @@ export default {
|
||||
}
|
||||
},
|
||||
popover() {
|
||||
if (!this.users || this.users.length < 0) {
|
||||
return []
|
||||
}
|
||||
return [
|
||||
...this.users.slice(3).map((session) => {
|
||||
return {
|
||||
|
||||
@@ -230,7 +230,7 @@ export default {
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .125s;
|
||||
}
|
||||
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user