src/app/modules/content/content.component.ts
Main content component
OnInit
OnDestroy
changeDetection | ChangeDetectionStrategy.OnPush |
selector | ccf-content |
styleUrls | ./content.component.scss |
templateUrl | ./content.component.html |
Properties |
|
Methods |
Inputs |
HostBindings |
constructor(model: ModelState, page: PageState, registration: RegistrationState, scene: SceneState, rootRef: ElementRef
|
||||||||||||||||||||||||||||
Creates an instance of content component.
Parameters :
|
disablePositionChange | |
Type : boolean
|
|
Default value : false
|
|
class |
Type : "ccf-content"
|
Default value : 'ccf-content'
|
HTML class name |
handleNodeDrag | ||||||
handleNodeDrag(event: NodeDragEvent)
|
||||||
Parameters :
Returns :
void
|
resetStage |
resetStage()
|
Method to reset registration block, crosshairs, and x,y,z information. Resets to initial registration state if provided
Returns :
void
|
setViewType | ||||||||
setViewType(is3DView: boolean)
|
||||||||
Sets view type
Parameters :
Returns :
void
|
Readonly clsName |
Type : string
|
Default value : 'ccf-content'
|
Decorators :
@HostBinding('class')
|
HTML class name |
debugMode |
Default value : false
|
Shows / hides the state debug component for testing purposes. |
Readonly is3DView$ |
Default value : this.model.viewType$.pipe(map((type) => type === '3d'))
|
Whether the view type is 3d or register |
isNarrowView |
Default value : false
|
Whether the content area is very narrow |
Readonly position$ |
Default value : this.model.position$.pipe(
map((p) => ({ x: Math.floor(p.x), y: Math.floor(p.y), z: Math.floor(p.z) })),
)
|
showDebugButtons |
Default value : !environment.production
|
Show debug buttons of content component |
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
HostBinding,
Input,
OnDestroy,
OnInit,
} from '@angular/core';
import { NodeDragEvent } from 'ccf-body-ui';
import { ResizeSensor } from 'css-element-queries';
import { distinctUntilKeyChanged, map } from 'rxjs/operators';
import { environment } from '../../../environments/environment';
import { ModelState } from '../../core/store/model/model.state';
import { PageState } from '../../core/store/page/page.state';
import { RegistrationState } from '../../core/store/registration/registration.state';
import { SceneState } from '../../core/store/scene/scene.state';
/**
* Main content component
*/
@Component({
selector: 'ccf-content',
templateUrl: './content.component.html',
styleUrls: ['./content.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ContentComponent implements OnInit, OnDestroy {
/** HTML class name */
@HostBinding('class') readonly clsName = 'ccf-content';
@Input() disablePositionChange = false;
readonly position$ = this.model.position$.pipe(
map((p) => ({ x: Math.floor(p.x), y: Math.floor(p.y), z: Math.floor(p.z) })),
);
/** Whether the view type is 3d or register */
readonly is3DView$ = this.model.viewType$.pipe(map((type) => type === '3d'));
readonly bounds$ = this.model.organDimensions$.pipe(
map((dims) => ({
x: Math.max(dims.x, this.model.defaultPosition.x + 40) / 1000,
y: Math.max(dims.y, this.model.defaultPosition.y + 40) / 1000,
z: Math.max(dims.z, this.model.defaultPosition.z + 40) / 1000,
})),
distinctUntilKeyChanged('x'),
distinctUntilKeyChanged('y'),
);
/** Whether the content area is very narrow */
isNarrowView = false;
/**
* Shows / hides the state debug component for testing purposes.
*/
debugMode = false;
/**
* Show debug buttons of content component
*/
showDebugButtons = !environment.production;
/** Resize detection */
private sensor!: ResizeSensor;
/**
* Creates an instance of content component.
*
* @param model The model state
* @param page The page state
* @param registration The registration state
* @param rootRef Component's root element
* @param cdr Change detector
*/
constructor(
readonly model: ModelState,
readonly page: PageState,
readonly registration: RegistrationState,
readonly scene: SceneState,
private readonly rootRef: ElementRef<HTMLElement>,
private readonly cdr: ChangeDetectorRef,
) {}
/**
* Sets up the resize sensor
*/
ngOnInit(): void {
this.sensor = new ResizeSensor(this.rootRef.nativeElement, ({ width }) => {
const isNarrowView = width < 440; // 27.5rem
if (this.isNarrowView !== isNarrowView) {
this.isNarrowView = isNarrowView;
this.cdr.markForCheck();
}
});
}
/**
* Detaches the resize sensor
*/
ngOnDestroy(): void {
this.sensor.detach();
}
/**
* Sets view type
*
* @param is3DView Set view type to '3d' if this is true otherwise set it to 'register'
*/
setViewType(is3DView: boolean): void {
this.model.setViewType(is3DView ? '3d' : 'register');
}
/**
* Method to reset registration block, crosshairs, and x,y,z information.
* Resets to initial registration state if provided
*/
resetStage(): void {
if (this.registration.snapshot.initialRegistration) {
this.registration.setToInitialRegistration();
} else {
this.model.setOrganDefaults();
}
this.model.setViewSide('anterior');
this.model.setViewType('register');
}
handleNodeDrag(event: NodeDragEvent): void {
if (event.node['@id'] === '#DraftPlacement') {
if (event.info.coordinate) {
const [a, b] = (event.info.coordinate as number[]).map((n) => n * 1000) as [number, number];
const { position, viewSide, organDimensions } = this.model.snapshot;
const dims = [organDimensions.x, organDimensions.y, organDimensions.z].map((n) => n / 2);
let newPosition = position;
switch (viewSide) {
case 'anterior':
newPosition = { x: a + dims[0], y: b + dims[1], z: position.z };
break;
case 'posterior':
newPosition = { x: -a + dims[0], y: b + dims[1], z: position.z };
break;
case 'left':
newPosition = { x: position.x, y: b + dims[1], z: -a + dims[2] };
break;
case 'right':
newPosition = { x: position.x, y: b + dims[1], z: a + dims[2] };
break;
}
this.model.setPosition(newPosition);
}
}
}
}
<div class="top-bar">
<ccf-stage-nav
[useDropdownMenu]="isNarrowView"
[view3D]="(is3DView$ | async) ?? false"
[side]="(model.viewSide$ | async)!"
(view3DChange)="setViewType($event)"
(sideChange)="model.setViewSide($any($event))"
>
</ccf-stage-nav>
<mat-icon
matRipple
[matRippleCentered]="true"
[matRippleUnbounded]="true"
matRippleColor="rgba(204, 204, 204, 0.25)"
class="icon reset"
(click)="resetStage()"
>refresh</mat-icon
>
</div>
<div class="main-content">
<ccf-body-ui
class="body-ui"
*ngIf="(model.viewType$ | async) === '3d'"
[scene]="(scene.nodes$ | async) ?? []"
[rotation]="(scene.rotation$ | async) ?? 0"
(rotationChange)="gizmo.rotation = $event[0]; gizmo.rotationX = $event[1]"
[bounds]="(bounds$ | async)!"
[zoom]="11.5"
[interactive]="true"
camera="perspective"
></ccf-body-ui>
<ccf-body-ui
class="body-ui"
*ngIf="(model.viewType$ | async) === 'register'"
[scene]="(scene.rotatedNodes$ | async) ?? []"
[interactive]="false"
[bounds]="(bounds$ | async)!"
[zoom]="11.5"
(nodeDrag)="handleNodeDrag($event)"
camera="orthographic"
></ccf-body-ui>
<div class="gizmo-area">
<ccf-body-ui
class="gizmo"
[scene]="(scene.gizmo$ | async)!"
[rotation]="(scene.rotation$ | async) ?? 0"
[interactive]="false"
[zoom]="9.5"
#gizmo
></ccf-body-ui>
<ccf-spatial-search-keyboard-ui-behavior
*ngIf="page.registrationStarted$ | async"
[delta]="1"
[shiftDelta]="2"
[position]="(position$ | async)!"
[disablePositionChange]="disablePositionChange"
></ccf-spatial-search-keyboard-ui-behavior>
<div class="position-display">
<div class="position">
<div class="x">X: {{ (position$ | async)?.x }}</div>
<div class="y">Y: {{ (position$ | async)?.y }}</div>
<div class="z">Z: {{ (position$ | async)?.z }}</div>
</div>
</div>
</div>
</div>
<ccf-store-debug *ngIf="showDebugButtons && debugMode" class="debug"> </ccf-store-debug>
<div
*ngIf="showDebugButtons"
style="
position: absolute;
bottom: 1rem;
display: flex;
flex-direction: row;
justify-content: space-between;
width: 7rem;
"
>
<button style="width: 3rem; font-size: 0.7rem" (click)="debugMode = !debugMode">State</button>
<button (click)="registration.setUseRegistrationCallback(true)">Use callback</button>
<button (click)="registration.setUseRegistrationCallback(false)">Use download</button>
</div>
./content.component.scss
:host {
display: block;
width: 100%;
height: 100%;
padding: 0.5rem;
overflow: hidden;
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
.reset {
transform: scaleX(-1);
cursor: pointer;
transition: 0.6s;
font-size: 2rem;
height: 2rem;
width: 2rem;
}
}
.sidebar {
.minimap-scene {
margin: 1.5rem;
width: 12.75rem;
height: 11rem;
::ng-deep .body-ui {
background-color: #232f3a;
}
}
ccf-spatial-search-keyboard-ui-behavior {
margin: 1.5rem;
display: flex;
justify-content: center;
}
ccf-xyz-position {
margin: 1.5rem;
padding-left: 5rem;
}
}
.main-content {
display: flex;
height: calc(100% - 1rem);
.body-ui {
flex: auto;
transition: opacity 1s;
}
.gizmo-area {
width: 7rem;
margin-top: 1.5rem;
margin-left: 1.5rem;
z-index: 10;
right: 1.5rem;
.position-display {
display: flex;
justify-content: center;
margin-bottom: 1.5rem;
}
.gizmo {
height: 5.5rem;
.hidden {
opacity: 0;
z-index: 0;
}
}
}
}
// Temporary for displaying dev information
.ccf-store-debug {
color: white;
position: absolute;
top: 0.5rem;
max-width: 55rem;
}
}