Compare commits
1 Commits
b84025d59b
...
enh/conten
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2755f0671 |
32
package-lock.json
generated
32
package-lock.json
generated
@@ -5254,29 +5254,24 @@
|
||||
}
|
||||
},
|
||||
"@nextcloud/event-bus": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/event-bus/-/event-bus-1.1.4.tgz",
|
||||
"integrity": "sha512-It27KzmUaSQ7w22nHFwOn8XgeVG0HYYOSNG9gs4UkP5VqcZ16m4ydt3GkMpWcyFec4OUjJc+yf7omRc3pNxsSw==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/event-bus/-/event-bus-1.2.0.tgz",
|
||||
"integrity": "sha512-pNS0R6Mvgj4WnbJQ8LYjxRjCbRndpwjHNyZYm0zl8U71gbHsUvQIIzTdW7WYg6Nz/FjAlrdmDXJDFLh1DDcIFA==",
|
||||
"requires": {
|
||||
"@types/semver": "^6.2.1",
|
||||
"@types/semver": "^7.1.0",
|
||||
"core-js": "^3.6.2",
|
||||
"semver": "^6.3.0"
|
||||
"semver": "^7.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/semver": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.2.1.tgz",
|
||||
"integrity": "sha512-+beqKQOh9PYxuHvijhVl+tIHvT6tuwOrE9m14zd+MT2A38KoKZhh7pYJ0SNleLtwDsiIxHDsIk9bv01oOxvSvA=="
|
||||
},
|
||||
"core-js": {
|
||||
"version": "3.6.5",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
|
||||
"integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5635,8 +5630,7 @@
|
||||
"@types/node": {
|
||||
"version": "13.13.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.4.tgz",
|
||||
"integrity": "sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA=="
|
||||
},
|
||||
"@types/normalize-package-data": {
|
||||
"version": "2.4.0",
|
||||
@@ -5656,6 +5650,14 @@
|
||||
"integrity": "sha512-boy4xPNEtiw6N3abRhBi/e7hNvy3Tt8E9ZRAQrwAGzoCGZS/1wjo9KY7JHhnfnEsG5wSjDbymCozUM9a3ea7OQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/semver": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.1.tgz",
|
||||
"integrity": "sha512-ooD/FJ8EuwlDKOI6D9HWxgIgJjMg2cuziXm/42npDC8y4NjxplBUn9loewZiBNCt44450lHAU0OSb51/UqXeag==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/stack-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"@nextcloud/auth": "^1.3.0",
|
||||
"@nextcloud/axios": "^1.3.2",
|
||||
"@nextcloud/dialogs": "^1.4.0",
|
||||
"@nextcloud/event-bus": "^1.2.0",
|
||||
"@nextcloud/files": "^1.1.0",
|
||||
"@nextcloud/initial-state": "^1.1.2",
|
||||
"@nextcloud/l10n": "^1.3.0",
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
|
||||
<template>
|
||||
<div class="board-wrapper">
|
||||
<div v-if="remoteUpdate" class="board-update-notification">
|
||||
{{ t('deck', 'The board has been updated by someone else.') }} <a @click="updateFromRemote">{{ t('deck', 'Update') }}</a>
|
||||
</div>
|
||||
<Controls :board="board" />
|
||||
<transition name="fade" mode="out-in">
|
||||
<div v-if="loading" key="loading" class="emptycontent">
|
||||
@@ -54,6 +57,9 @@ import { Container, Draggable } from 'vue-smooth-dnd'
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import Controls from '../Controls'
|
||||
import Stack from './Stack'
|
||||
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
|
||||
|
||||
const BOARD_POLLING_INTERVAL = 1000
|
||||
|
||||
export default {
|
||||
name: 'Board',
|
||||
@@ -81,6 +87,7 @@ export default {
|
||||
...mapState({
|
||||
board: state => state.currentBoard,
|
||||
showArchived: state => state.showArchived,
|
||||
remoteUpdate: state => state.stack.remoteUpdate,
|
||||
}),
|
||||
...mapGetters([
|
||||
'canEdit',
|
||||
@@ -100,6 +107,18 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.fetchData()
|
||||
setInterval(() => {
|
||||
this.$store.dispatch('poll', this.id)
|
||||
}, BOARD_POLLING_INTERVAL)
|
||||
|
||||
subscribe('deck:card:modified', (card) => {
|
||||
console.log('card modified', card.lastModified)
|
||||
this.$store.dispatch('updateBoardLastModified', { ...this.board, lastModified: card.lastModified })
|
||||
})
|
||||
subscribe('deck:stack:modified', (stack) => {
|
||||
console.log('card modified', stack.lastModified)
|
||||
this.$store.dispatch('updateBoardLastModified', { ...this.board, lastModified: stack.lastModified })
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
async fetchData() {
|
||||
@@ -113,6 +132,10 @@ export default {
|
||||
this.loading = false
|
||||
},
|
||||
|
||||
updateFromRemote() {
|
||||
this.$store.dispatch('pollApply', this.id)
|
||||
},
|
||||
|
||||
onDropStack({ removedIndex, addedIndex }) {
|
||||
this.$store.dispatch('orderStack', { stack: this.stacksByBoard[removedIndex], removedIndex, addedIndex })
|
||||
},
|
||||
@@ -193,4 +216,33 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.board-update-notification {
|
||||
position: absolute;
|
||||
background-color: var(--color-primary-light);
|
||||
border-radius: var(--border-radius-large);
|
||||
z-index: 1000;
|
||||
padding: 4px 20px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
margin: 10px auto;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0px);
|
||||
|
||||
animation: slideFromTop var(--animation-slow) ease-out forwards;
|
||||
a {
|
||||
font-weight: bold;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@keyframes slideFromTop
|
||||
{
|
||||
from {transform: translate(-50%, -100px); opacity: 0;}
|
||||
to { transform: translate(-50%, 0); opacity: 1;}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import { CardApi } from './../services/CardApi'
|
||||
import Vue from 'vue'
|
||||
import { emit } from '@nextcloud/event-bus'
|
||||
|
||||
const apiClient = new CardApi()
|
||||
|
||||
@@ -102,6 +103,7 @@ export default {
|
||||
if (existingIndex !== -1) {
|
||||
const existingCard = state.cards.find(_card => _card.id === card.id)
|
||||
Vue.set(state.cards, existingIndex, Object.assign({}, existingCard, card))
|
||||
emit('deck:card:modified', card)
|
||||
} else {
|
||||
state.cards.push(card)
|
||||
}
|
||||
@@ -111,11 +113,13 @@ export default {
|
||||
if (existingIndex !== -1) {
|
||||
state.cards.splice(existingIndex, 1)
|
||||
}
|
||||
emit('deck:card:modified', card)
|
||||
},
|
||||
updateCard(state, card) {
|
||||
const existingIndex = state.cards.findIndex(_card => _card.id === card.id)
|
||||
if (existingIndex !== -1) {
|
||||
Vue.set(state.cards, existingIndex, Object.assign({}, state.cards[existingIndex], card))
|
||||
emit('deck:card:modified', card)
|
||||
}
|
||||
},
|
||||
updateCardsReorder(state, cards) {
|
||||
@@ -126,19 +130,24 @@ export default {
|
||||
Vue.set(state.cards[existingIndex], 'stackId', newCard.stackId)
|
||||
}
|
||||
}
|
||||
emit('deck:card:modified', cards[cards.length - 1])
|
||||
},
|
||||
assignCardToUser(state, user) {
|
||||
assignCardToUser(state, { card, user }) {
|
||||
const existingIndex = state.cards.findIndex(_card => _card.id === user.cardId)
|
||||
if (existingIndex !== -1) {
|
||||
state.cards[existingIndex].assignedUsers.push(user)
|
||||
}
|
||||
// FIXME: workaround since we have no server time on assignments
|
||||
emit('deck:card:modified', { ...card, lastModified: Date.now() / 1000 })
|
||||
},
|
||||
removeUserFromCard(state, user) {
|
||||
removeUserFromCard(state, { card, user }) {
|
||||
const existingIndex = state.cards.findIndex(_card => _card.id === user.cardId)
|
||||
if (existingIndex !== -1) {
|
||||
const foundIndex = state.cards[existingIndex].assignedUsers.findIndex(_user => _user.id === user.id)
|
||||
if (foundIndex !== -1) {
|
||||
state.cards[existingIndex].assignedUsers.splice(foundIndex, 1)
|
||||
// FIXME: workaround since we have no server time on assignments
|
||||
emit('deck:card:modified', { ...card, lastModified: Date.now() / 1000 })
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -148,17 +157,20 @@ export default {
|
||||
Vue.set(state.cards[existingIndex], property, card[property])
|
||||
}
|
||||
Vue.set(state.cards[existingIndex], 'lastModified', Date.now() / 1000)
|
||||
emit('deck:card:modified', card)
|
||||
},
|
||||
cardIncreaseAttachmentCount(state, cardId) {
|
||||
const existingIndex = state.cards.findIndex(_card => _card.id === cardId)
|
||||
if (existingIndex !== -1) {
|
||||
Vue.set(state.cards[existingIndex], 'attachmentCount', state.cards[existingIndex].attachmentCount + 1)
|
||||
emit('deck:card:modified', state.cards[existingIndex])
|
||||
}
|
||||
},
|
||||
cardDecreaseAttachmentCount(state, cardId) {
|
||||
const existingIndex = state.cards.findIndex(_card => _card.id === cardId)
|
||||
if (existingIndex !== -1) {
|
||||
Vue.set(state.cards[existingIndex], 'attachmentCount', state.cards[existingIndex].attachmentCount - 1)
|
||||
emit('deck:card:modified', state.cards[existingIndex])
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -213,11 +225,11 @@ export default {
|
||||
},
|
||||
async assignCardToUser({ commit }, { card, assignee }) {
|
||||
const user = await apiClient.assignUser(card.id, assignee.userId, assignee.type)
|
||||
commit('assignCardToUser', user)
|
||||
commit('assignCardToUser', { card, user })
|
||||
},
|
||||
async removeUserFromCard({ commit }, { card, assignee }) {
|
||||
const user = await apiClient.removeUser(card.id, assignee.userId, assignee.type)
|
||||
commit('removeUserFromCard', user)
|
||||
commit('removeUserFromCard', { card, user })
|
||||
},
|
||||
async addLabel({ commit }, data) {
|
||||
await apiClient.assignLabelToCard(data)
|
||||
|
||||
@@ -317,6 +317,11 @@ export default new Vuex.Store({
|
||||
const storedBoard = await apiClient.updateBoard(board)
|
||||
commit('addBoard', storedBoard)
|
||||
},
|
||||
updateBoardLastModified({ commit }, board) {
|
||||
commit('addBoard', board)
|
||||
commit('setCurrentBoard', board)
|
||||
|
||||
},
|
||||
createBoard({ commit }, boardData) {
|
||||
apiClient.createBoard(boardData)
|
||||
.then((board) => {
|
||||
|
||||
@@ -21,14 +21,18 @@
|
||||
*/
|
||||
|
||||
import Vue from 'vue'
|
||||
import { BoardApi } from './../services/BoardApi'
|
||||
import { StackApi } from './../services/StackApi'
|
||||
import applyOrderToArray from './../helpers/applyOrderToArray'
|
||||
import { emit } from '@nextcloud/event-bus'
|
||||
|
||||
const boardApiClient = new BoardApi()
|
||||
const apiClient = new StackApi()
|
||||
|
||||
export default {
|
||||
state: {
|
||||
stacks: [],
|
||||
remoteUpdate: null,
|
||||
},
|
||||
getters: {
|
||||
stacksByBoard: state => (id) => {
|
||||
@@ -36,6 +40,12 @@ export default {
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
clearStacks(state) {
|
||||
state.stacks = []
|
||||
},
|
||||
updateRemote(state, response) {
|
||||
Vue.set(state, 'remoteUpdate', response)
|
||||
},
|
||||
addStack(state, stack) {
|
||||
const existingIndex = state.stacks.findIndex(_stack => _stack.id === stack.id)
|
||||
if (existingIndex !== -1) {
|
||||
@@ -73,6 +83,7 @@ export default {
|
||||
OC.Notification.showTemporary('Failed to change order')
|
||||
console.error(err.response.data.message)
|
||||
commit('orderStack', { stack, addedIndex, removedIndex })
|
||||
emit('deck:stack:modified', { ...stack, lastModified: Date.now() / 1000 })
|
||||
})
|
||||
},
|
||||
async loadStacks({ commit }, boardId) {
|
||||
@@ -89,13 +100,57 @@ export default {
|
||||
}
|
||||
delete stack.cards
|
||||
commit('addStack', stack)
|
||||
emit('deck:stack:modified', { ...stack, lastModified: Date.now() / 1000 })
|
||||
}
|
||||
},
|
||||
async poll({ commit, rootState, state }, boardId) {
|
||||
if (!rootState.currentBoard) {
|
||||
return
|
||||
}
|
||||
// TODO: set If-Modified-Since header
|
||||
const board = await boardApiClient.loadById(rootState.currentBoard.id)
|
||||
|
||||
console.debug('[deck] poll: remote(' + board.lastModified + ') local(' + rootState.currentBoard.lastModified + ') update(' + state.remoteUpdate?.lastModified + ')')
|
||||
if (rootState.currentBoard.lastModified >= board.lastModified || state.remoteUpdate?.lastModified === board.lastModified) {
|
||||
console.debug('[deck] poll: no new data for board ' + board.title)
|
||||
return
|
||||
}
|
||||
|
||||
let call = 'loadStacks'
|
||||
if (this.state.showArchived === true) {
|
||||
call = 'loadArchivedStacks'
|
||||
}
|
||||
const stacks = await apiClient[call](boardId)
|
||||
board.stacks = stacks
|
||||
commit('updateRemote', board)
|
||||
console.debug('[deck] poll: applied new data for board ' + board.title)
|
||||
|
||||
},
|
||||
async pollApply({ commit, state }, boardId) {
|
||||
commit('clearCards')
|
||||
commit('clearStacks')
|
||||
// TODO: trigger board updated at on every operation
|
||||
// event bus deck:board:modified board
|
||||
// event bus deck:card:modified card
|
||||
// event bus deck:stack:modified stack
|
||||
for (const i in state.remoteUpdate.stacks) {
|
||||
const stack = state.remoteUpdate.stacks[i]
|
||||
for (const j in stack.cards) {
|
||||
commit('addCard', stack.cards[j])
|
||||
}
|
||||
delete stack.cards
|
||||
commit('addStack', stack)
|
||||
}
|
||||
delete state.remoteUpdate.stacks
|
||||
commit('setCurrentBoard', state.remoteUpdate)
|
||||
commit('updateRemote', null)
|
||||
},
|
||||
createStack({ commit }, stack) {
|
||||
stack.boardId = this.state.currentBoard.id
|
||||
apiClient.createStack(stack)
|
||||
.then((createdStack) => {
|
||||
commit('addStack', createdStack)
|
||||
emit('deck:stack:modified', { ...createdStack, lastModified: Date.now() / 1000 })
|
||||
})
|
||||
},
|
||||
deleteStack({ commit }, stack) {
|
||||
@@ -103,12 +158,14 @@ export default {
|
||||
.then((stack) => {
|
||||
commit('deleteStack', stack)
|
||||
commit('moveStackToTrash', stack)
|
||||
emit('deck:stack:modified', { ...stack, lastModified: Date.now() / 1000 })
|
||||
})
|
||||
},
|
||||
updateStack({ commit }, stack) {
|
||||
apiClient.updateStack(stack)
|
||||
.then((stack) => {
|
||||
commit('updateStack', stack)
|
||||
emit('deck:stack:modified', { ...stack, lastModified: Date.now() / 1000 })
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user