diff --git a/css/style.scss b/css/style.scss index 40fba0d0a..c5aa3ef50 100644 --- a/css/style.scss +++ b/css/style.scss @@ -526,11 +526,6 @@ input.input-inline { margin-right: 2px; } } - - button { - padding: 22px; - margin: 0; - } } a { @@ -1537,7 +1532,7 @@ input.input-inline { table { margin-bottom: 10px; border-collapse: collapse; - + thead { background-color: var(--color-background-dark, $color-lightgrey); } diff --git a/package.json b/package.json index 4806d8fe2..731429543 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,9 @@ "vue": "^2.5.16", "vue-click-outside": "^1.0.7", "vue-infinite-loading": "^2.4.1", + "vue-multiselect": "^2.1.3", "vue-router": "^3.0.1", + "vue-smooth-dnd": "^0.2.8", "vuex": "^3.0.1", "vuex-router-sync": "^5.0.0" }, @@ -52,7 +54,7 @@ "babel-eslint": "^10.0.1", "babel-jest": "^23.6.0", "babel-loader": "^8.0.4", - "css-loader": "^1.0.1", + "css-loader": "^2.0.1", "eslint": "^4.19.1", "eslint-config-standard": "^11.0.0", "eslint-friendly-formatter": "^4.0.1", @@ -63,13 +65,13 @@ "eslint-plugin-standard": "^3.1.0", "eslint-plugin-vue": "^4.5.0", "extract-text-webpack-plugin": "^3.0.2", - "file-loader": "^2.0.0", + "file-loader": "^3.0.1", "jest": "^23.6.0", "jest-serializer-vue": "^2.0.2", "mini-css-extract-plugin": "^0.5.0", "node-sass": "^4.10.0", "prettier-eslint": "^8.8.2", - "raw-loader": "^0.5.1", + "raw-loader": "^1.0.0", "sass-loader": "^7.1.0", "stylelint": "^8.4.0", "stylelint-config-recommended-scss": "^3.2.0", diff --git a/src/App.vue b/src/App.vue index c62fa66d3..922357dea 100644 --- a/src/App.vue +++ b/src/App.vue @@ -22,14 +22,12 @@ @@ -71,9 +69,17 @@ export default { computed: { ...mapState({ navShown: state => state.navShown, - sidebarShown: state => state.sidebarShown, + sidebarShownState: state => state.sidebarShown, currentBoard: state => state.currentBoard - }) + }), + // TODO: properly handle sidebar showing for route subview and board sidebar + sidebarRouterView() { + console.log(this.$route) + return this.$route.name === 'card' || this.$route.name === 'board.details' + }, + sidebarShown() { + return this.sidebarRouterView || this.sidebarShownState + } }, provide: function() { return { diff --git a/src/components/Controls.vue b/src/components/Controls.vue index 443ac0ea6..a5f0c37b7 100644 --- a/src/components/Controls.vue +++ b/src/components/Controls.vue @@ -35,6 +35,9 @@ {{ board.title }} +
+ +
@@ -52,6 +55,9 @@ export default { methods: { toggleNav() { this.$store.dispatch('toggleNav') + }, + toggleSidebar: function() { + this.$store.dispatch('toggleSidebar') } } } @@ -67,4 +73,14 @@ export default { position: static; } + .board-actions { + flex-grow: 1; + order: 100; + display: flex; + justify-content: flex-end; + } + button.icon-settings { + width: 44px; + } + diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue new file mode 100644 index 000000000..e802cb48d --- /dev/null +++ b/src/components/Sidebar.vue @@ -0,0 +1,53 @@ + + + + + + + diff --git a/src/components/board/Board.vue b/src/components/board/Board.vue index f5d8d903d..9d83334de 100644 --- a/src/components/board/Board.vue +++ b/src/components/board/Board.vue @@ -24,21 +24,68 @@
- board {{ board.title }}
- + + + + +

{{ stack.title }}

+ + + + + +
+
+
+
+
+

{{ t('deck', 'Board not found')}}

+

+ + diff --git a/src/components/cards/CardBadges.vue b/src/components/cards/CardBadges.vue new file mode 100644 index 000000000..4fb829e59 --- /dev/null +++ b/src/components/cards/CardBadges.vue @@ -0,0 +1,71 @@ + + + + + + diff --git a/src/components/cards/CardItem.vue b/src/components/cards/CardItem.vue new file mode 100644 index 000000000..cbc3cef0d --- /dev/null +++ b/src/components/cards/CardItem.vue @@ -0,0 +1,131 @@ + + + + + + + diff --git a/src/components/cards/LabelTag.vue b/src/components/cards/LabelTag.vue new file mode 100644 index 000000000..1db9fb66c --- /dev/null +++ b/src/components/cards/LabelTag.vue @@ -0,0 +1,40 @@ + + + + + + diff --git a/src/mixins/color.js b/src/mixins/color.js new file mode 100644 index 000000000..7f2397ebd --- /dev/null +++ b/src/mixins/color.js @@ -0,0 +1,85 @@ +/* + * @copyright Copyright (c) 2018 Julius Härtl + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +export default { + methods: { + hexToRgb(hex) { + let result = /^#?([A-Fa-f\d]{2})([A-Fa-f\d]{2})([A-Fa-f\d]{2})$/i.exec(hex) + return result + ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } : null + }, + rgb2hls(rgb) { + // RGB2HLS by Garry Tan + // http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c + const r = rgb.r / 255 + const g = rgb.g / 255 + const b = rgb.b / 255 + let max = Math.max(r, g, b) + let min = Math.min(r, g, b) + let h + let s + let l = (max + min) / 2 + + if (max === min) { + h = s = 0 // achromatic + } else { + const d = max - min + s = l > 0.5 ? d / (2 - max - min) : d / (max + min) + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0) + break + case g: + h = (b - r) / d + 2 + break + case b: + h = (r - g) / d + 4 + break + } + h /= 6 + } + return { + h, l, s + } + }, + textColor(hex) { + + let rgb = this.hexToRgb(hex) + if (rgb === null) { + return '#000000' + } + const { l } = this.rgb2hls(rgb) + + if (l < 0.5) { + return '#ffffff' + } else { + return '#000000' + } + + } + + } +} diff --git a/src/router.js b/src/router.js index 836a34d7a..29c295607 100644 --- a/src/router.js +++ b/src/router.js @@ -26,6 +26,9 @@ import { generateUrl } from 'nextcloud-server/dist/router' import { BOARD_FILTERS } from './store/main' import Boards from './components/boards/Boards' import Board from './components/board/Board' +import Sidebar from './components/Sidebar' +import BoardSidebar from './components/board/BoardSidebar' +import CardSidebar from './components/card/CardSidebar' Vue.use(Router) @@ -65,12 +68,34 @@ export default new Router({ { path: '/boards/:id', name: 'board', - component: Board, - props: (route) => { - return { - id: parseInt(route.params.id, 10) + components: { + default: Board, + sidebar: Sidebar + }, + props: { + default: (route) => { + return { + id: parseInt(route.params.id, 10) + } } - } + }, + children: [ + { + path: 'details', + name: 'board.details', + components: { + default: Boards, + sidebar: BoardSidebar + } + }, + { + path: 'cards/:cardId', + name: 'card', + components: { + sidebar: CardSidebar + } + } + ] } ] }) diff --git a/src/services/StackApi.js b/src/services/StackApi.js new file mode 100644 index 000000000..5b2785117 --- /dev/null +++ b/src/services/StackApi.js @@ -0,0 +1,109 @@ +/* + * @copyright Copyright (c) 2018 Michael Weimann + * + * @author Michael Weimann + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +import axios from 'nextcloud-axios' + +/** + * This class handles all the api communication with the Deck backend. + */ +export class BoardApi { + + url(url) { + url = `/apps/deck${url}` + return OC.generateUrl(url) + } + + /** + * Updates a board. + * + * @param {Board} board + * @return Promise + */ + updateBoard(board) { + return axios.put(this.url(`/boards/${board.id}`), board) + .then( + (response) => { + return Promise.resolve(response.data) + }, + (err) => { + return Promise.reject(err) + } + ) + .catch((err) => { + return Promise.reject(err) + }) + } + + /** + * Creates a new board. + * + * @param {{String title, String color, String hashedColor}} boardData The board data to send. + * hashedColor is the color in hex format, e.g. "#ff0000" + * color is the same color without the "#" + * @return Promise + */ + createBoard(boardData) { + return axios.post(this.url('/boards'), boardData) + .then( + (response) => { + return Promise.resolve(response.data) + }, + (err) => { + return Promise.reject(err) + } + ) + .catch((err) => { + return Promise.reject(err) + }) + } + + loadBoards() { + return axios.get(this.url('/boards')) + .then( + (response) => { + return Promise.resolve(response.data) + }, + (err) => { + return Promise.reject(err) + } + ) + .catch((err) => { + return Promise.reject(err) + }) + } + + loadById(id) { + return axios.get(this.url(`/boards/${id}`)) + .then( + (response) => { + return Promise.resolve(response.data) + }, + (err) => { + return Promise.reject(err) + } + ) + .catch((err) => { + return Promise.reject(err) + }) + } + +}