Compare commits

...

1 Commits

Author SHA1 Message Date
Julius Härtl
a2755f0671 WIP: Naive approach on content sync
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2020-08-01 09:12:43 +02:00
6 changed files with 148 additions and 19 deletions

32
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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>

View File

@@ -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)

View File

@@ -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) => {

View File

@@ -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 })
})
},
},