Merge pull request #8070 from opf/feature/bim-view-toggle

Readd view toggle component
pull/8078/head
Henriette Dinger 5 years ago committed by GitHub
commit fbdef4729d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 42
      frontend/src/app/modules/ifc_models/ifc-viewer/ifc-viewer.component.ts
  2. 9
      frontend/src/app/modules/ifc_models/openproject-ifc-models.module.ts
  3. 15
      frontend/src/app/modules/ifc_models/openproject-ifc-models.routes.ts
  4. 3
      frontend/src/app/modules/ifc_models/pages/viewer/ifc-viewer-page.component.html
  5. 94
      frontend/src/app/modules/ifc_models/view-toggle/bim-view-toggle-dropdown.directive.ts
  6. 62
      frontend/src/app/modules/ifc_models/view-toggle/bim-view-toggle.component.ts
  7. 91
      frontend/src/app/modules/ifc_models/view-toggle/bim-view.service.ts
  8. 102
      modules/bim/spec/features/bim_navigation_spec.rb
  9. 9
      modules/bim/spec/support/pages/ifc_models/show_default.rb

@ -28,7 +28,7 @@
/// <reference path="../xeokit/xeokit.d.ts" /> /// <reference path="../xeokit/xeokit.d.ts" />
import {Component, ElementRef, OnInit} from '@angular/core'; import {ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit} from '@angular/core';
import {XeokitServer} from "../xeokit/xeokit-server"; import {XeokitServer} from "../xeokit/xeokit-server";
import {GonService} from "core-app/modules/common/gon/gon.service"; import {GonService} from "core-app/modules/common/gon/gon.service";
@ -36,15 +36,18 @@ import {GonService} from "core-app/modules/common/gon/gon.service";
@Component({ @Component({
selector: 'ifc-viewer', selector: 'ifc-viewer',
template: ` template: `
<div class="ifc-model-viewer--container"> <div class="ifc-model-viewer--container">
<div class="ifc-model-viewer--toolbar-container"></div> <div class="ifc-model-viewer--toolbar-container"></div>
<canvas class="ifc-model-viewer--model-canvas"></canvas> <canvas class="ifc-model-viewer--model-canvas"></canvas>
</div> </div>
<canvas class="ifc-model-viewer--nav-cube-canvas"></canvas> <canvas class="ifc-model-viewer--nav-cube-canvas"></canvas>
` `,
changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class IFCViewerComponent implements OnInit { export class IFCViewerComponent implements OnInit, OnDestroy {
private viewerUI:any;
constructor(private Gon:GonService, constructor(private Gon:GonService,
private elementRef:ElementRef) { private elementRef:ElementRef) {
} }
@ -54,7 +57,7 @@ export class IFCViewerComponent implements OnInit {
import('@xeokit/xeokit-viewer/dist/main').then((XeokitViewerModule:any) => { import('@xeokit/xeokit-viewer/dist/main').then((XeokitViewerModule:any) => {
let server = new XeokitServer(); let server = new XeokitServer();
let viewerUI = new XeokitViewerModule.BIMViewer(server, { this.viewerUI = new XeokitViewerModule.BIMViewer(server, {
canvasElement: element.find(".ifc-model-viewer--model-canvas")[0], // WebGL canvas canvasElement: element.find(".ifc-model-viewer--model-canvas")[0], // WebGL canvas
explorerElement: jQuery(".ifc-model-viewer--tree-panel")[0], // Left panel explorerElement: jQuery(".ifc-model-viewer--tree-panel")[0], // Left panel
toolbarElement: element.find(".ifc-model-viewer--toolbar-container")[0], // Toolbar toolbarElement: element.find(".ifc-model-viewer--toolbar-container")[0], // Toolbar
@ -62,13 +65,30 @@ export class IFCViewerComponent implements OnInit {
sectionPlanesOverviewCanvasElement: element.find(".ifc-model-viewer--section-planes-overview-canvas")[0] sectionPlanesOverviewCanvasElement: element.find(".ifc-model-viewer--section-planes-overview-canvas")[0]
}); });
viewerUI.on("queryPicked", (event:any) => { this.viewerUI.on("queryPicked", (event:any) => {
const entity = event.entity; // Entity const entity = event.entity; // Entity
const metaObject = event.metaObject; // MetaObject const metaObject = event.metaObject; // MetaObject
alert(`Query result:\n\nObject ID = ${entity.id}\nIFC type = "${metaObject.type}"`); alert(`Query result:\n\nObject ID = ${entity.id}\nIFC type = "${metaObject.type}"`);
}); });
viewerUI.loadProject(this.Gon.get('ifc_models', 'projects') as any [0]["id"]); this.viewerUI.loadProject(this.Gon.get('ifc_models', 'projects') as any [0]["id"]);
}); });
} }
ngOnDestroy():void {
if (!this.viewerUI) {
return;
}
this.viewerUI._bcfViewpointsPlugin.destroy();
this.viewerUI._canvasContextMenu.destroy();
this.viewerUI._objectContextMenu.destroy();
while (this.viewerUI.viewer._plugins.length > 0) {
const plugin = this.viewerUI.viewer._plugins[0];
plugin.destroy();
}
this.viewerUI.viewer.scene.destroy();
}
} }

@ -33,7 +33,10 @@ import {IFCViewerComponent} from './ifc-viewer/ifc-viewer.component';
import {IFC_ROUTES} from "core-app/modules/ifc_models/openproject-ifc-models.routes"; import {IFC_ROUTES} from "core-app/modules/ifc_models/openproject-ifc-models.routes";
import {IFCViewerPageComponent} from "core-app/modules/ifc_models/pages/viewer/ifc-viewer-page.component"; import {IFCViewerPageComponent} from "core-app/modules/ifc_models/pages/viewer/ifc-viewer-page.component";
import {BCFContainerComponent} from "core-app/modules/ifc_models/bcf/container/bcf-container.component"; import {BCFContainerComponent} from "core-app/modules/ifc_models/bcf/container/bcf-container.component";
import {BimViewToggleDropdownDirective} from "core-app/modules/ifc_models/view-toggle/bim-view-toggle-dropdown.directive";
import {BimViewToggleComponent} from "core-app/modules/ifc_models/view-toggle/bim-view-toggle.component";
import {EmptyComponent} from "core-app/modules/ifc_models/empty/empty-component"; import {EmptyComponent} from "core-app/modules/ifc_models/empty/empty-component";
import {BimViewService} from "core-app/modules/ifc_models/view-toggle/bim-view.service";
@NgModule({ @NgModule({
imports: [ imports: [
@ -43,8 +46,6 @@ import {EmptyComponent} from "core-app/modules/ifc_models/empty/empty-component"
states: IFC_ROUTES states: IFC_ROUTES
}) })
], ],
providers: [
],
declarations: [ declarations: [
// Pages // Pages
IFCViewerPageComponent, IFCViewerPageComponent,
@ -53,6 +54,10 @@ import {EmptyComponent} from "core-app/modules/ifc_models/empty/empty-component"
EmptyComponent, EmptyComponent,
BCFContainerComponent, BCFContainerComponent,
// View selector
BimViewToggleComponent,
BimViewToggleDropdownDirective,
IFCViewerComponent IFCViewerComponent
], ],
exports: [ exports: [

@ -50,6 +50,9 @@ export const IFC_ROUTES:Ng2StateDeclaration[] = [
name: 'bim.space.list', name: 'bim.space.list',
url: '/list', url: '/list',
component: IFCViewerPageComponent, component: IFCViewerPageComponent,
data: {
viewRoute: 'bim.space.defaults',
},
views: { views: {
list: { component: BCFContainerComponent } list: { component: BCFContainerComponent }
} }
@ -58,6 +61,9 @@ export const IFC_ROUTES:Ng2StateDeclaration[] = [
name: 'bim.space.defaults', name: 'bim.space.defaults',
url: '/defaults', url: '/defaults',
component: IFCViewerPageComponent, component: IFCViewerPageComponent,
data: {
viewRoute: 'bim.space.defaults',
},
views: { views: {
viewer: { component: IFCViewerComponent }, viewer: { component: IFCViewerComponent },
list: { component: BCFContainerComponent } list: { component: BCFContainerComponent }
@ -67,6 +73,9 @@ export const IFC_ROUTES:Ng2StateDeclaration[] = [
name: 'bim.space.defaults.model', name: 'bim.space.defaults.model',
url: '/model', url: '/model',
component: IFCViewerPageComponent, component: IFCViewerPageComponent,
data: {
viewRoute: 'bim.space.defaults',
},
views: { views: {
// Retarget and by that override the grandparent views // Retarget and by that override the grandparent views
// https://ui-router.github.io/guide/views#relative-parent-state // https://ui-router.github.io/guide/views#relative-parent-state
@ -77,6 +86,9 @@ export const IFC_ROUTES:Ng2StateDeclaration[] = [
name: 'bim.space.show', name: 'bim.space.show',
url: '/{model_id:[0-9]+}', url: '/{model_id:[0-9]+}',
component: IFCViewerPageComponent, component: IFCViewerPageComponent,
data: {
viewRoute: 'bim.space.show',
},
views: { views: {
viewer: { component: IFCViewerComponent }, viewer: { component: IFCViewerComponent },
list: { component: BCFContainerComponent } list: { component: BCFContainerComponent }
@ -86,6 +98,9 @@ export const IFC_ROUTES:Ng2StateDeclaration[] = [
name: 'bim.space.show.model', name: 'bim.space.show.model',
url: '/model', url: '/model',
component: IFCViewerPageComponent, component: IFCViewerPageComponent,
data: {
viewRoute: 'bim.space.show',
},
views: { views: {
// Retarget and by that override the grandparent views // Retarget and by that override the grandparent views
// https://ui-router.github.io/guide/views#relative-parent-state // https://ui-router.github.io/guide/views#relative-parent-state

@ -5,6 +5,9 @@
</h2> </h2>
</div> </div>
<ul class="toolbar-items"> <ul class="toolbar-items">
<li class="toolbar-item">
<bim-view-toggle-button></bim-view-toggle-button>
</li>
<li class="toolbar-item" <li class="toolbar-item"
*ngIf="manageAllowed"> *ngIf="manageAllowed">
<a class="button" <a class="button"

@ -0,0 +1,94 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 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 docs/COPYRIGHT.rdoc for more details.
//++
import {OPContextMenuService} from "core-components/op-context-menu/op-context-menu.service";
import {Directive, ElementRef} from "@angular/core";
import {OpContextMenuTrigger} from "core-components/op-context-menu/handlers/op-context-menu-trigger.directive";
import {I18nService} from "core-app/modules/common/i18n/i18n.service";
import {
bimListViewIdentifier,
bimSplitViewIdentifier,
bimViewerViewIdentifier
} from "core-app/modules/ifc_models/view-toggle/bim-view-toggle.component";
import {StateService} from "@uirouter/core";
import {BimViewService} from "core-app/modules/ifc_models/view-toggle/bim-view.service";
@Directive({
selector: '[bimViewDropdown]'
})
export class BimViewToggleDropdownDirective extends OpContextMenuTrigger {
constructor(readonly elementRef:ElementRef,
readonly opContextMenu:OPContextMenuService,
readonly bimView:BimViewService,
readonly I18n:I18nService,
readonly state:StateService) {
super(elementRef, opContextMenu);
}
protected open(evt:JQuery.TriggeredEvent) {
this.buildItems();
this.opContextMenu.show(this, evt);
}
public get locals() {
return {
items: this.items,
contextMenuId: 'bim-view-context-menu'
};
}
private buildItems() {
const current = this.bimView.current;
const viewRoute = this.state.current.data.viewRoute;
this.items = [bimViewerViewIdentifier, bimListViewIdentifier, bimSplitViewIdentifier]
.map(key => {
return {
hidden: key === current,
linkText: this.bimView.text[key],
onClick: () => {
switch (key) {
case bimListViewIdentifier:
this.state.go('bim.space.list');
break;
case bimViewerViewIdentifier:
this.state.go(viewRoute + '.model');
break;
case bimSplitViewIdentifier:
this.state.go(viewRoute);
break;
}
return true;
}
};
});
}
}

@ -0,0 +1,62 @@
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 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 docs/COPYRIGHT.rdoc for more details.
// ++
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {I18nService} from 'core-app/modules/common/i18n/i18n.service';
import {BimViewService} from "core-app/modules/ifc_models/view-toggle/bim-view.service";
export const bimListViewIdentifier = 'list';
export const bimViewerViewIdentifier = 'viewer';
export const bimSplitViewIdentifier = 'split';
@Component({
template: `
<ng-container *ngIf="(view$ | async) as current">
<button class="button"
id="bim-view-toggle-button"
bimViewDropdown>
<span class="button--text"
aria-hidden="true"
[textContent]="bimView.text[current]">
</span>
<op-icon icon-classes="button--icon icon-small icon-pulldown"></op-icon>
</button>
</ng-container>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'bim-view-toggle-button'
})
export class BimViewToggleComponent {
view$ = this.bimView.view.values$();
constructor(readonly I18n:I18nService,
readonly bimView:BimViewService) {
}
}

@ -0,0 +1,91 @@
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 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 docs/COPYRIGHT.rdoc for more details.
// ++
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Injectable, OnDestroy, OnInit} from '@angular/core';
import {I18nService} from 'core-app/modules/common/i18n/i18n.service';
import {DynamicBootstrapper} from "core-app/globals/dynamic-bootstrapper";
import {
WorkPackageViewDisplayRepresentationService,
wpDisplayCardRepresentation,
wpDisplayListRepresentation, wpDisplayRepresentation
} from "core-app/modules/work_packages/routing/wp-view-base/view-services/wp-view-display-representation.service";
import {untilComponentDestroyed} from "ng2-rx-componentdestroyed";
import {WorkPackageViewTimelineService} from "core-app/modules/work_packages/routing/wp-view-base/view-services/wp-view-timeline.service";
import {combineLatest, Observable} from "rxjs";
import {StateService, TransitionService} from "@uirouter/core";
import {input} from "reactivestates";
import {OpenprojectIFCModelsModule} from "core-app/modules/ifc_models/openproject-ifc-models.module";
export const bimListViewIdentifier = 'list';
export const bimViewerViewIdentifier = 'viewer';
export const bimSplitViewIdentifier = 'split';
export type BimViewState = 'list'|'viewer'|'split';
@Injectable({ providedIn: OpenprojectIFCModelsModule })
export class BimViewService implements OnDestroy {
public view = input<BimViewState>();
public text:any = {
list: this.I18n.t('js.ifc_models.views.list'),
viewer: this.I18n.t('js.ifc_models.views.viewer'),
split: this.I18n.t('js.ifc_models.views.split')
};
private transitionFn:Function;
constructor(readonly I18n:I18nService,
readonly transitions:TransitionService,
readonly state:StateService) {
this.detectView();
this.transitionFn = this.transitions.onSuccess({}, (transition) => {
this.detectView();
});
}
get current():BimViewState {
return this.view.getValueOr(bimSplitViewIdentifier);
}
private detectView() {
if (this.state.current.name === 'bim.space.list') {
this.view.putValue(bimListViewIdentifier);
} else if (this.state.includes('bim.**.model')) {
this.view.putValue(bimViewerViewIdentifier);
} else {
this.view.putValue(bimSplitViewIdentifier);
}
}
ngOnDestroy() {
this.transitionFn();
}
}

@ -0,0 +1,102 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2020 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-2017 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 docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require_relative '../support/pages/ifc_models/show'
require_relative '../support/pages/ifc_models/show_default'
describe 'BIM navigation spec', type: :feature, js: true do
let(:project) { FactoryBot.create :project, enabled_module_names: [:bim, :work_package_tracking] }
let!(:work_package) { FactoryBot.create(:work_package, project: project) }
let(:role) { FactoryBot.create(:role, permissions: %i[view_ifc_models manage_ifc_models view_work_packages]) }
let(:user) do
FactoryBot.create :user,
member_in_project: project,
member_through_role: role
end
let!(:model) do
FactoryBot.create(:ifc_model_converted,
project: project,
uploader: user)
end
let(:card_view) { ::Pages::WorkPackageCards.new(project) }
shared_examples 'can switch from split to viewer to list-only' do
before do
login_as(user)
model_page.visit!
model_page.finished_loading
end
context 'deep link on the page' do
before do
login_as(user)
model_page.visit!
model_page.finished_loading
end
it 'can switch from split to viewer only to list' do
# Should be at split view
model_page.model_viewer_visible true
model_page.model_viewer_shows_a_toolbar true
model_page.page_shows_a_toolbar true
model_page.sidebar_shows_viewer_menu true
expect(page).to have_selector('.wp-cards-container')
card_view.expect_work_package_listed work_package
# Go to viewer only
model_page.switch_view 'Viewer only'
model_page.model_viewer_visible true
expect(page).to have_no_selector('.wp-cards-container')
# Go to list only
model_page.switch_view 'List only'
model_page.model_viewer_visible false
expect(page).to have_selector('.wp-cards-container')
card_view.expect_work_package_listed work_package
end
end
end
context 'on default page' do
let(:model_page) { ::Pages::IfcModels::ShowDefault.new project }
it_behaves_like 'can switch from split to viewer to list-only'
end
context 'on show page' do
let(:model_page) { ::Pages::IfcModels::Show.new project, model.id }
it_behaves_like 'can switch from split to viewer to list-only'
end
end

@ -80,6 +80,15 @@ module Pages
end end
end end
def switch_view(value)
page.find('#bim-view-toggle-button').click
page.find('.menu-item', text: value).click
end
def expect_view_toggle_at(value)
expect(page).to have_selector('#bim-view-toggle-button', text: value)
end
private private
def toolbar_items def toolbar_items

Loading…
Cancel
Save