diff --git a/frontend/src/app/core/main-menu/submenu.service.ts b/frontend/src/app/core/main-menu/submenu.service.ts index 941a9cd5cc61..95a9cf3cc2e8 100644 --- a/frontend/src/app/core/main-menu/submenu.service.ts +++ b/frontend/src/app/core/main-menu/submenu.service.ts @@ -6,16 +6,30 @@ import { StateService } from '@uirouter/core'; export class SubmenuService { constructor(protected $state:StateService) {} - reloadSubmenu():void { + reloadSubmenu(selectedQueryId:string|null):void { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment const menuIdentifier:string|undefined = this.$state.current.data.sideMenuOptions?.sidemenuId; if (menuIdentifier) { const menu = (document.getElementById(menuIdentifier) as HTMLElement&TurboElement); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const sideMenuOptions = this.$state.$current.data?.sideMenuOptions as { hardReloadOnBaseRoute?:boolean, defaultQuery?:string }; const currentSrc = menu.getAttribute('src'); if (currentSrc && menu) { const frameUrl = new URL(currentSrc); + const defaultQuery = sideMenuOptions.defaultQuery; + + if (selectedQueryId) { + // If there is a default query passed in the route definition, it means that id passed as argument and not as parameter, + // e.g. calendars/:id, team_planner/:id, ... + // Otherwise, we will just replace the params + if (defaultQuery) { + frameUrl.search = `?id=${selectedQueryId}`; + } else { + frameUrl.search = `?query_id=${selectedQueryId}`; + } + } // Override the frame src to enforce a reload menu.setAttribute('src', frameUrl.href); diff --git a/frontend/src/app/features/bim/ifc_models/openproject-ifc-models.routes.ts b/frontend/src/app/features/bim/ifc_models/openproject-ifc-models.routes.ts index 1886303dbdb6..595099fbd751 100644 --- a/frontend/src/app/features/bim/ifc_models/openproject-ifc-models.routes.ts +++ b/frontend/src/app/features/bim/ifc_models/openproject-ifc-models.routes.ts @@ -35,11 +35,18 @@ import { WorkPackagesBaseComponent } from 'core-app/features/work-packages/routi import { BcfSplitLeftComponent } from 'core-app/features/bim/ifc_models/bcf/split/left/bcf-split-left.component'; import { BcfSplitRightComponent } from 'core-app/features/bim/ifc_models/bcf/split/right/bcf-split-right.component'; +export const sidemenuId = 'bim_sidemenu'; + +export const sideMenuOptions = { + sidemenuId, + hardReloadOnBaseRoute: true, +}; + export const IFC_ROUTES:Ng2StateDeclaration[] = [ { name: 'bim', parent: 'optional_project', - url: '/bcf?query_id&query_props&models&viewpoint', + url: '/bcf?query_id&query_props&models&viewpoint&name', abstract: true, component: WorkPackagesBaseComponent, redirectTo: 'bim.partitioned.list', @@ -49,6 +56,7 @@ export const IFC_ROUTES:Ng2StateDeclaration[] = [ query_props: { type: 'opQueryString', dynamic: true }, models: { type: 'opQueryString', dynamic: true }, viewpoint: { type: 'int', dynamic: true }, + name: { type: 'string', dynamic: true }, }, }, { @@ -58,6 +66,7 @@ export const IFC_ROUTES:Ng2StateDeclaration[] = [ component: IFCViewerPageComponent, data: { bodyClasses: 'router--bim', + sideMenuOptions, }, }, { @@ -67,6 +76,7 @@ export const IFC_ROUTES:Ng2StateDeclaration[] = [ baseRoute: 'bim.partitioned.list', newRoute: 'bim.partitioned.list.new', partition: '-split', + sideMenuOptions, }, reloadOnSearch: false, views: { @@ -83,6 +93,7 @@ export const IFC_ROUTES:Ng2StateDeclaration[] = [ allowMovingInEditMode: true, partition: '-left-only', successState: 'bim.partitioned.show', + sideMenuOptions, }, views: { 'content-left': { component: WorkPackageNewFullViewComponent } }, }, @@ -92,6 +103,7 @@ export const IFC_ROUTES:Ng2StateDeclaration[] = [ data: { baseRoute: 'bim.partitioned.list', partition: '-left-only', + sideMenuOptions, }, reloadOnSearch: false, redirectTo: 'bim.partitioned.show.details', diff --git a/frontend/src/app/features/boards/board/board-partitioned-page/board-partitioned-page.component.ts b/frontend/src/app/features/boards/board/board-partitioned-page/board-partitioned-page.component.ts index f27b4581cbb4..9aa35c277222 100644 --- a/frontend/src/app/features/boards/board/board-partitioned-page/board-partitioned-page.component.ts +++ b/frontend/src/app/features/boards/board/board-partitioned-page/board-partitioned-page.component.ts @@ -9,9 +9,11 @@ import { ToolbarButtonComponentDefinition, ViewPartitionState, } from 'core-app/features/work-packages/routing/partitioned-query-space-page/partitioned-query-space-page.component'; -import { StateService, TransitionService } from '@uirouter/core'; +import { + StateService, + TransitionService, +} from '@uirouter/core'; import { BoardFilterComponent } from 'core-app/features/boards/board/board-filter/board-filter.component'; -import { Board } from 'core-app/features/boards/board/board'; import { ToastService } from 'core-app/shared/components/toaster/toast.service'; import { HalResourceNotificationService } from 'core-app/features/hal/services/hal-resource-notification.service'; import { BoardService } from 'core-app/features/boards/board/board.service'; @@ -19,13 +21,10 @@ import { DragAndDropService } from 'core-app/shared/helpers/drag-and-drop/drag-a import { WorkPackageFilterButtonComponent } from 'core-app/features/work-packages/components/wp-buttons/wp-filter-button/wp-filter-button.component'; import { ZenModeButtonComponent } from 'core-app/features/work-packages/components/wp-buttons/zen-mode-toggle-button/zen-mode-toggle-button.component'; import { BoardsMenuButtonComponent } from 'core-app/features/boards/board/toolbar-menu/boards-menu-button.component'; -import { RequestSwitchmap } from 'core-app/shared/helpers/rxjs/request-switchmap'; -import { componentDestroyed } from '@w11k/ngx-componentdestroyed'; import { catchError, finalize, take, - tap, } from 'rxjs/operators'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin'; @@ -35,11 +34,7 @@ import { BoardFiltersService } from 'core-app/features/boards/board/board-filter import { CardViewHandlerRegistry } from 'core-app/features/work-packages/components/wp-card-view/event-handler/card-view-handler-registry'; import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; import { OpTitleService } from 'core-app/core/html/op-title.service'; -import { - EMPTY, - Observable, - of, -} from 'rxjs'; +import { EMPTY } from 'rxjs'; import { SubmenuService } from 'core-app/core/main-menu/submenu.service'; export function boardCardViewHandlerFactory(injector:Injector) { @@ -249,6 +244,6 @@ export class BoardPartitionedPageComponent extends UntilDestroyedMixin { } private reloadSidemenu():void { - this.submenuService.reloadSubmenu(); + this.submenuService.reloadSubmenu(null); } } diff --git a/frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts b/frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts index 6f6d19353782..6652798f7b0b 100644 --- a/frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts @@ -262,7 +262,7 @@ export class WorkPackagesListService { // Reload the query, and then reload the menu this.reloadQuery(createdQuery).subscribe(() => { this.states.changes.queries.next(createdQuery.id); - this.reloadSidemenu(); + this.reloadSidemenu(createdQuery.id); }); return createdQuery; @@ -313,7 +313,7 @@ export class WorkPackagesListService { if (queryAccessibleByUser) { void this.$state.go('.', { query_id: query.id, query_props: null }, { reload: true }); this.states.changes.queries.next(query.id); - this.reloadSidemenu(); + this.reloadSidemenu(query.id); } else { this.navigateToDefaultQuery(query); } @@ -344,7 +344,7 @@ export class WorkPackagesListService { this.toastService.addSuccess(this.I18n.t('js.notice_successful_update')); this.states.changes.queries.next(query.id!); - this.reloadSidemenu(); + this.reloadSidemenu(query.id); }); return promise; @@ -458,11 +458,11 @@ export class WorkPackagesListService { void this.loadDefaultQuery(projectId); this.states.changes.queries.next(query.id); - this.reloadSidemenu(); + this.reloadSidemenu(null); } } - private reloadSidemenu():void { - this.submenuService.reloadSubmenu(); + private reloadSidemenu(selectedQueryId:string|null):void { + this.submenuService.reloadSubmenu(selectedQueryId); } } diff --git a/frontend/src/app/shared/components/op-view-select/op-static-queries.service.ts b/frontend/src/app/shared/components/op-view-select/op-static-queries.service.ts index 75d16b7a9377..51566ed8c9d0 100644 --- a/frontend/src/app/shared/components/op-view-select/op-static-queries.service.ts +++ b/frontend/src/app/shared/components/op-view-select/op-static-queries.service.ts @@ -93,7 +93,7 @@ export class StaticQueriesService { if (this.$state.params.name) { const nameKey = this.$state.params.name as string; - return this.I18n.t(`js.queries.${nameKey}`); + return this.I18n.t(`js.work_packages.default_queries.${nameKey}`); } } @@ -143,33 +143,6 @@ export class StaticQueriesService { }, view: 'WorkPackagesTable', }, - { - title: this.text.all_open, - uiSref: 'bim.partitioned.list', - uiParams: { - query_id: undefined, - query_props: '{"c":["id","subject","bcfThumbnail","type","status","assignee","updatedAt"],"t":"id:desc"}', - }, - view: 'Bim', - }, - { - title: this.text.latest_activity, - uiSref: 'bim.partitioned.list', - uiParams: { - query_id: undefined, - query_props: '{"c":["id","subject","bcfThumbnail","type","status","assignee","updatedAt"],"t":"updatedAt:desc","f":[{"n":"status","o":"o","v":[]}]}', - }, - view: 'Bim', - }, - { - title: this.text.recently_created, - uiSref: 'bim.partitioned.list', - uiParams: { - query_id: undefined, - query_props: '{"c":["id","subject","bcfThumbnail","type","status","assignee","createdAt"],"t":"createdAt:desc","f":[{"n":"status","o":"o","v":[]}]}', - }, - view: 'Bim', - }, ]; const projectIdentifier = this.CurrentProject.identifier; @@ -237,24 +210,6 @@ export class StaticQueriesService { isEnterprise: true, ...this.eeGuardedShareWithMeRoute, }, - { - title: this.text.created_by_me, - uiSref: 'bim.partitioned.list', - uiParams: { - query_id: undefined, - query_props: '{"c":["id","subject","bcfThumbnail","type","status","assignee","updatedAt"],"t":"id:desc","f":[{"n":"status","o":"o","v":[]},{"n":"author","o":"=","v":["me"]}]}', - }, - view: 'Bim', - }, - { - title: this.text.assigned_to_me, - uiSref: 'bim.partitioned.list', - uiParams: { - query_id: undefined, - query_props: '{"c":["id","subject","bcfThumbnail","type","status","author","updatedAt"],"t":"id:desc","f":[{"n":"status","o":"o","v":[]},{"n":"assigneeOrGroup","o":"=","v":["me"]}]}', - }, - view: 'Bim', - }, ]; } diff --git a/modules/bim/app/controllers/bim/ifc_models/ifc_models_controller.rb b/modules/bim/app/controllers/bim/ifc_models/ifc_models_controller.rb index a2b62136608a..8eacce3c9761 100644 --- a/modules/bim/app/controllers/bim/ifc_models/ifc_models_controller.rb +++ b/modules/bim/app/controllers/bim/ifc_models/ifc_models_controller.rb @@ -197,9 +197,10 @@ def prepare_form(ifc_model) end def frontend_redirect(model_ids) - props = '{"c":["id","subject","bcfThumbnail","type","status","assignee","updatedAt"],"t":"id:desc"}' + props = Bim::Menus::DefaultQueryGeneratorService.new.call redirect_to bcf_project_frontend_path(models: JSON.dump(Array(model_ids)), - query_props: props) + query_props: props[:query_props], + name: props[:name]) end def find_all_ifc_models diff --git a/modules/bim/app/controllers/bim/menus_controller.rb b/modules/bim/app/controllers/bim/menus_controller.rb new file mode 100644 index 000000000000..5ac4a2eeea95 --- /dev/null +++ b/modules/bim/app/controllers/bim/menus_controller.rb @@ -0,0 +1,39 @@ +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2023 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ +module ::Bim + class MenusController < ApplicationController + before_action :find_project_by_project_id, + :authorize + + def show + @submenu_menu_items = ::Bim::Menu.new(project: @project, params:).menu_items + + render layout: nil + end + end +end diff --git a/modules/bim/app/menus/bim/menu.rb b/modules/bim/app/menus/bim/menu.rb new file mode 100644 index 000000000000..0041f2901ce8 --- /dev/null +++ b/modules/bim/app/menus/bim/menu.rb @@ -0,0 +1,57 @@ +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2023 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ +module Bim + class Menu < Submenu + attr_reader :view_type, :project, :params + + def initialize(project: nil, params: nil) + @view_type = "bim" + @project = project + @params = params + + super(view_type:, project:, params:) + end + + def default_queries + query_generator = Bim::Menus::DefaultQueryGeneratorService.new + Bim::Menus::DefaultQueryGeneratorService::QUERY_OPTIONS.filter_map do |query_key| + params = query_generator.call(query_key:) + next if params.nil? + + menu_item( + I18n.t("js.work_packages.default_queries.#{query_key}"), + params + ) + end + end + + def query_path(query_params) + bcf_project_frontend_path(project, query_params) + end + end +end diff --git a/modules/bim/app/services/bim/menus/default_query_generator_service.rb b/modules/bim/app/services/bim/menus/default_query_generator_service.rb new file mode 100644 index 000000000000..98922a006164 --- /dev/null +++ b/modules/bim/app/services/bim/menus/default_query_generator_service.rb @@ -0,0 +1,113 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module ::Bim + module Menus + class DefaultQueryGeneratorService + DEFAULT_QUERY = :all_open + + QUERY_OPTIONS = [ + DEFAULT_QUERY, + :latest_activity, + :recently_created, + :created_by_me, + :assigned_to_me + ].freeze + + def call(query_key: DEFAULT_QUERY) + params = self.class.assign_params(query_key) + + return if params.nil? + + { query_props: params.to_json, name: query_key } + end + + class << self + def all_open_query + { + c: %w[id subject bcfThumbnail type status assignee updatedAt], + t: "id:desc" + } + end + + def latest_activity_query + { + c: %w[id subject bcfThumbnail type status assignee updatedAt], + t: "updatedAt:desc", + f: [{ "n" => "status", "o" => "o", "v" => [] }] + } + end + + def recently_created_query + { + c: %w[id subject bcfThumbnail type status assignee createdAt], + t: "createdAt:desc", + f: [{ "n" => "status", "o" => "o", "v" => [] }] + } + end + + def created_by_me_query + { + c: %w[id subject bcfThumbnail type status author updatedAt], + t: "id:desc", + f: [{ "n" => "status", "o" => "o", "v" => [] }, + { "n" => "author", "o" => "=", "v" => ["me"] }] + } + end + + def assigned_to_me_query + { + c: %w[id subject bcfThumbnail type status author updatedAt], + t: "id:desc", + f: [{ "n" => "status", "o" => "o", "v" => [] }, + { "n" => "assigneeOrGroup", "o" => "=", "v" => ["me"] }] + } + end + + def assign_params(query_key) + case query_key + when DEFAULT_QUERY + all_open_query + when :latest_activity + latest_activity_query + when :recently_created + recently_created_query + when :created_by_me + return unless User.current.logged? + + created_by_me_query + when :assigned_to_me + return unless User.current.logged? + + assigned_to_me_query + end + end + end + end + end +end diff --git a/modules/bim/app/views/bim/ifc_models/ifc_models/_panels.html.erb b/modules/bim/app/views/bim/ifc_models/ifc_models/_panels.html.erb deleted file mode 100644 index 9cef69501581..000000000000 --- a/modules/bim/app/views/bim/ifc_models/ifc_models/_panels.html.erb +++ /dev/null @@ -1,17 +0,0 @@ -
diff --git a/modules/bim/app/views/bim/menus/_menu.html.erb b/modules/bim/app/views/bim/menus/_menu.html.erb new file mode 100644 index 000000000000..e4afbbc2b525 --- /dev/null +++ b/modules/bim/app/views/bim/menus/_menu.html.erb @@ -0,0 +1,9 @@ + <%= turbo_frame_tag "bim_sidemenu", + src: bcf_project_bcf_menu_path(@project, **params.permit(:query_props, :query_id, :name)), + target: '_top', + data: { turbo: false }, + loading: :lazy %> + +