Skip to content

Commit 0072010

Browse files
committed
Finish first version of Angular example
1 parent 9513591 commit 0072010

26 files changed

+619
-70
lines changed

examples/simple-angular/src/app/app.component.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,8 @@
77
width: 100vw;
88
height: 100vh;
99
}
10+
11+
.stats{
12+
visibility: collapse;
13+
right: 0;
14+
}

examples/simple-angular/src/app/app.component.html

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@
22
<mat-sidenav #sidenav opened="false" mode="side" opened>
33
<app-toolbar
44
(onOpenIfc)="loadIfc($event)"
5+
(onActivateClipping)="activateClipping()"
56
></app-toolbar>
67
</mat-sidenav>
7-
<mat-sidenav-content>
8+
<mat-sidenav-content >
89
<button mat-fab color="accent" (click)="sidenav.toggle()" class="navbar-button">
910
<mat-icon>menu</mat-icon>
1011
</button>
11-
<div id="viewer-container"></div>
12+
<app-spatial-tree #spatialTree [ifc]="ifcViewer"></app-spatial-tree>
13+
<app-context-menu
14+
(onAddClippingPlane)="addClippingPlane($event)"
15+
(onRemoveClippingPlane)="removeClippingPlane($event)"
16+
></app-context-menu>
17+
<div #threeContainer id="viewer-container"></div>
1218
</mat-sidenav-content>
1319
</mat-sidenav-container>
Lines changed: 62 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
1+
import { Component, OnInit, ViewChild, ElementRef, HostListener } from '@angular/core';
22
import { MatSidenav } from '@angular/material/sidenav';
33
import { IfcViewerAPI } from 'web-ifc-viewer';
4+
import { SpatialTreeComponent } from './spatial-tree/spatial-tree.component';
5+
46

57
@Component({
68
selector: 'app-root',
@@ -9,63 +11,69 @@ import { IfcViewerAPI } from 'web-ifc-viewer';
911
})
1012
export class AppComponent implements OnInit {
1113
title = 'ifcjs-angular-example';
12-
viewer?: IfcViewerAPI;
13-
@ViewChild('sidenav', {static: true}) serverContentInput?: MatSidenav;
14+
ifcViewer?: IfcViewerAPI;
15+
16+
@ViewChild('spatialTree', { static: true }) spatialTree?: SpatialTreeComponent;
17+
@ViewChild('sidenav', { static: true }) sidenav?: MatSidenav;
18+
@ViewChild('threeContainer', { static: true }) container?: ElementRef;
19+
20+
ngOnInit() {
21+
if (this.sidenav) this.sidenav.close();
22+
this.setupScene();
23+
this.setupInputs();
24+
}
25+
26+
setupScene() {
27+
const container = this.getContainer();
28+
if (!container) return this.notFoundError('container');
29+
this.ifcViewer = new IfcViewerAPI({ container });
30+
this.ifcViewer.addAxes();
31+
this.ifcViewer.addGrid();
32+
this.ifcViewer.addStats('position:absolute;bottom:0px;left:0px;z-index:1;');
33+
this.ifcViewer.setWasmPath('assets/');
34+
}
35+
36+
setupInputs() {
37+
const container = this.getContainer();
38+
if (!container) return this.notFoundError('container');
39+
container.onclick = this.handleClick;
40+
container.ondblclick = this.handleDoubleClick;
41+
container.onmousemove = this.handleMouseMove;
42+
}
43+
44+
activateClipping() {
45+
this.ifcViewer?.toggleClippingPlanes();
46+
}
47+
48+
addClippingPlane(event: Event) {
49+
this.ifcViewer?.addClippingPlane();
50+
}
51+
52+
removeClippingPlane(event: Event) {
53+
this.ifcViewer?.removeClippingPlane();
54+
}
55+
56+
loadIfc(file: File) {
57+
this.ifcViewer?.loadIfc(file);
58+
}
59+
60+
private handleClick = (event: Event) => {
61+
const id = this.ifcViewer?.getModelID();
62+
if (typeof id === 'number') this.spatialTree?.updateSpatialTree(id);
63+
};
1464

15-
ngOnInit(){
65+
private handleDoubleClick = (event: Event) => {};
1666

17-
if(this.serverContentInput) this.serverContentInput.close();
18-
19-
const container = document.getElementById("viewer-container")!;
20-
this.viewer = new IfcViewerAPI({container});
21-
console.log(this.viewer);
22-
this.viewer.addAxes();
23-
this.viewer.addGrid();
24-
this.viewer.setWasmPath("assets/");
67+
private handleMouseMove = (_event: Event) => {
68+
this.ifcViewer?.preselectIfcItem();
69+
};
2570

26-
// const url = "https://raw.githubusercontent.com/IFCjs/test-ifc-files/main/Revit/TESTED_Simple_project_01.ifc";
27-
// this.viewer.loadIfcUrl(url);
28-
29-
//Setup loader
30-
// const loadIfc = async (event) => {
31-
// await viewer.loadIfc(event.target.files[0], true);
32-
// }
33-
34-
// const inputElement = document.createElement('input');
35-
// inputElement.setAttribute('type', 'file');
36-
// inputElement.classList.add('hidden');
37-
// inputElement.addEventListener('change', loadIfc, false);
38-
// document.body.appendChild(inputElement);
39-
40-
// const handleKeyDown = (event) => {
41-
// viewer.removeClippingPlane();
42-
// };
43-
44-
// window.onmousemove = viewer.preselectIfcItem;
45-
// window.onkeydown = handleKeyDown;
46-
// window.ondblclick = viewer.addClippingPlane;
47-
48-
//Setup UI
49-
// const loadButton = createSideMenuButton('./resources/folder-icon.svg');
50-
// loadButton.addEventListener('click', () => {
51-
// loadButton.blur();
52-
// inputElement.click();
53-
// });
54-
55-
// const sectionButton = createSideMenuButton('./resources/section-plane-down.svg');
56-
// sectionButton.addEventListener('click', () => {
57-
// sectionButton.blur();
58-
// viewer.toggleClippingPlanes();
59-
// });
60-
61-
// const dropBoxButton = createSideMenuButton('./resources/dropbox-icon.svg');
62-
// dropBoxButton.addEventListener('click', () => {
63-
// dropBoxButton.blur();
64-
// viewer.openDropboxWindow();
65-
// });
71+
private notFoundError(item: string) {
72+
throw new Error(`ERROR: ${item} could not be found!`);
6673
}
6774

68-
loadIfc(file: File){
69-
this.viewer?.loadIfc(file);
75+
private getContainer() {
76+
if (!this.container) return null;
77+
return this.container.nativeElement as HTMLElement;
7078
}
7179
}

examples/simple-angular/src/app/app.module.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,40 @@ import { ToolbarComponent } from './toolbar/toolbar.component';
77
import { MatSidenavModule } from '@angular/material/sidenav';
88
import { MatButtonModule } from '@angular/material/button';
99
import { MatIconModule } from '@angular/material/icon';
10+
import { ContextMenuComponent } from './context-menu/context-menu.component';
11+
import {MatMenuModule} from '@angular/material/menu';
12+
import {MatTreeModule} from '@angular/material/tree';
13+
import {MatCheckboxModule} from '@angular/material/checkbox';
14+
import { SpatialTreeComponent } from './spatial-tree/spatial-tree.component';
15+
import {MatCardModule} from '@angular/material/card';
16+
import {MatExpansionModule} from '@angular/material/expansion';
17+
import { ClickStopPropagationDirective } from './directives/click-stop-propagation.directive';
18+
import {MatDividerModule} from '@angular/material/divider';
19+
import {MatListModule} from '@angular/material/list';
20+
import {MatTableModule} from '@angular/material/table';
1021

1122
@NgModule({
1223
declarations: [
1324
AppComponent,
14-
ToolbarComponent
25+
ToolbarComponent,
26+
ContextMenuComponent,
27+
SpatialTreeComponent,
28+
ClickStopPropagationDirective,
1529
],
1630
imports: [
1731
BrowserModule,
1832
BrowserAnimationsModule,
1933
MatSidenavModule,
2034
MatButtonModule,
21-
MatIconModule
35+
MatIconModule,
36+
MatMenuModule,
37+
MatTreeModule,
38+
MatCheckboxModule,
39+
MatCardModule,
40+
MatExpansionModule,
41+
MatDividerModule,
42+
MatListModule,
43+
MatTableModule
2244
],
2345
providers: [],
2446
bootstrap: [AppComponent]

examples/simple-angular/src/app/context-menu/context-menu.component.css

Whitespace-only changes.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!-- <button mat-raised-button [matMenuTriggerFor]="menu">Menu</button> -->
2+
<mat-menu #menu="matMenu" class="contextMenu">
3+
<button (click)="onAddClippingPlane($event)" mat-menu-item>Add clipping plane</button>
4+
<button (click)="onRemoveClippingPlane($event)" mat-menu-item>Remove clipping plane</button>
5+
</mat-menu>
6+
7+
<!-- an hidden div is created to set the position of appearance of the menu-->
8+
<div style="visibility: hidden; position: fixed;"
9+
[style.left]="menuTopLeftPosition.x"
10+
[style.top]="menuTopLeftPosition.y"
11+
[matMenuTriggerFor]="menu"></div>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { ContextMenuComponent } from './context-menu.component';
4+
5+
describe('ContextMenuComponent', () => {
6+
let component: ContextMenuComponent;
7+
let fixture: ComponentFixture<ContextMenuComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
declarations: [ ContextMenuComponent ]
12+
})
13+
.compileComponents();
14+
});
15+
16+
beforeEach(() => {
17+
fixture = TestBed.createComponent(ContextMenuComponent);
18+
component = fixture.componentInstance;
19+
fixture.detectChanges();
20+
});
21+
22+
it('should create', () => {
23+
expect(component).toBeTruthy();
24+
});
25+
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Component, OnInit, Output, ViewChild, EventEmitter } from '@angular/core';
2+
import { MatMenuTrigger } from '@angular/material/menu';
3+
4+
//source: https://marco.dev/angular-right-click-menu
5+
6+
@Component({
7+
selector: 'app-context-menu',
8+
templateUrl: './context-menu.component.html',
9+
styleUrls: ['./context-menu.component.css']
10+
})
11+
export class ContextMenuComponent implements OnInit {
12+
13+
menuTopLeftPosition = {x: '0', y: '0'}
14+
@ViewChild(MatMenuTrigger, {static: true}) matMenuTrigger?: MatMenuTrigger;
15+
@Output('onAddClippingPlane') onAddClipping = new EventEmitter();
16+
@Output('onRemoveClippingPlane') onRemoveClipping = new EventEmitter();
17+
18+
constructor() {
19+
this.setupEvents();
20+
}
21+
22+
ngOnInit(): void { }
23+
24+
onAddClippingPlane(event: Event){
25+
console.log(event);
26+
this.onAddClipping.emit(event);
27+
}
28+
29+
onRemoveClippingPlane(event: Event){
30+
console.log(event);
31+
this.onRemoveClipping.emit(event);
32+
}
33+
34+
setupEvents(){
35+
window.oncontextmenu = this.popup;
36+
}
37+
38+
popup = (event: MouseEvent) => {
39+
// preventDefault avoids to show the visualization of the right-click menu of the browser
40+
event.preventDefault();
41+
this.matMenuTrigger?.closeMenu();
42+
setTimeout(() => this.openMenu(event), 200);
43+
}
44+
45+
openMenu(event: MouseEvent){
46+
if(!this.matMenuTrigger) return;
47+
this.menuTopLeftPosition.x = event.clientX + 'px';
48+
this.menuTopLeftPosition.y = event.clientY + 'px';
49+
this.matMenuTrigger.openMenu();
50+
}
51+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { ClickStopPropagationDirective } from './click-stop-propagation.directive';
2+
3+
describe('ClickStopPropagationDirective', () => {
4+
it('should create an instance', () => {
5+
const directive = new ClickStopPropagationDirective();
6+
expect(directive).toBeTruthy();
7+
});
8+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {Directive, HostListener} from "@angular/core";
2+
3+
@Directive({
4+
selector: '[click-stop-propagation]'
5+
})
6+
export class ClickStopPropagationDirective {
7+
8+
@HostListener("click", ["$event"])
9+
public onClick(event: any): void
10+
{
11+
event.stopPropagation();
12+
}
13+
14+
}

0 commit comments

Comments
 (0)