diff --git a/appinfo/routes.php b/appinfo/routes.php index 6e2c273087..b75e268f07 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -13,6 +13,8 @@ ['name' => 'page#indexBoard', 'url' => '/board/{boardId}', 'verb' => 'GET'], ['name' => 'page#indexBoardDetails', 'url' => '/board/{boardId}/details', 'verb' => 'GET'], ['name' => 'page#indexCard', 'url' => '/board/{boardId}/card/{cardId}', 'verb' => 'GET'], + // Declared after the more specific board routes so those keep priority. + ['name' => 'page#indexBoardView', 'url' => '/board/{boardId}/{viewMode}', 'verb' => 'GET', 'requirements' => ['viewMode' => 'kanban|gantt']], ['name' => 'page#redirectToCard', 'url' => '/card/{cardId}', 'verb' => 'GET'], diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index 1cad275201..3aae08727c 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -101,6 +101,12 @@ public function indexBoardDetails(int $boardId): TemplateResponse { return $this->index(); } + #[NoAdminRequired] + #[NoCSRFRequired] + public function indexBoardView(int $boardId, string $viewMode): TemplateResponse { + return $this->index(); + } + #[NoAdminRequired] #[NoCSRFRequired] public function indexCard(int $cardId): TemplateResponse { diff --git a/src/components/board/Board.vue b/src/components/board/Board.vue index 3268e9bc3c..29f87acc2d 100644 --- a/src/components/board/Board.vue +++ b/src/components/board/Board.vue @@ -124,6 +124,15 @@ export default { type: Number, default: null, }, + /** + * View to open the board in, coming from the /board/:id/:viewMode + * route. Applied for this session only, so linking to a view does not + * change the user's stored preference for the board. + */ + initialViewMode: { + type: String, + default: null, + }, }, data() { return { @@ -186,6 +195,11 @@ export default { await this.$store.dispatch('loadBoardById', this.id) await this.$store.dispatch('loadStacks', this.id) + // Only after loadBoardById, as the mutation needs currentBoard + if (this.initialViewMode) { + this.$store.commit('setViewModeForSession', this.initialViewMode) + } + const routeCardId = this.$route?.params?.cardId ? parseInt(this.$route.params.cardId) : null // If an archived card is requested, and we cannot find it in the current we load the archived stacks instead if (routeCardId && !this.$store.getters.cardById(routeCardId)) { diff --git a/src/router.js b/src/router.js index 6b7387ace7..398937b46f 100644 --- a/src/router.js +++ b/src/router.js @@ -75,6 +75,25 @@ const router = new Router({ navFilter: BOARD_FILTERS.SHARED, }, }, + { + // Opens a board directly in a given view, so a board can be linked + // to as e.g. a Gantt chart. The view is applied for this session + // only and does not overwrite the user's stored preference. + path: '/board/:id/:viewMode(kanban|gantt)', + name: 'board.view', + components: { + default: Board, + sidebar: Sidebar, + }, + props: { + default: (route) => { + return { + id: parseInt(route.params.id, 10), + initialViewMode: route.params.viewMode, + } + }, + }, + }, { path: '/board/:id', name: 'board', diff --git a/src/store/main.js b/src/store/main.js index 8a735e1604..327fb2c392 100644 --- a/src/store/main.js +++ b/src/store/main.js @@ -309,6 +309,20 @@ export default function storeFactory() { Vue.set(state.viewModeByBoard, state.currentBoard.id, mode) localStorage.setItem(`deck.viewMode.${state.currentBoard.id}`, mode) }, + /** + * Set the view mode without storing it as the user's preference. + * + * Used by the /board/:id/:viewMode routes, so that opening a link + * to a specific view does not change how the recipient sees that + * board afterwards. + * + * @param {object} state the store state + * @param {string} mode the view mode to apply + */ + setViewModeForSession(state, mode) { + if (!state.currentBoard) return + Vue.set(state.viewModeByBoard, state.currentBoard.id, mode) + }, }, actions: { setFullApp({ commit }, isFullApp) {