Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react-components): initial support for Observations #4631

Merged
merged 23 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2d1d8ab
feat(react-components): initial support for Observations
haakonflatval-cognite Jun 21, 2024
35d3016
chore: lint fix
haakonflatval-cognite Jun 21, 2024
683f598
chore: Add shameful type cast to please compiler
haakonflatval-cognite Jun 21, 2024
fefd837
chore: use RevealButtons import in Toolbar story
haakonflatval-cognite Jun 24, 2024
bd8e935
chore: revert irrelevant change
haakonflatval-cognite Jun 24, 2024
77c2144
chore: remove some "as"-es
haakonflatval-cognite Jun 24, 2024
e6ec4ff
chore: refactor a bit to conform better to architecture
haakonflatval-cognite Jun 25, 2024
33c019f
chore: add missing file
haakonflatval-cognite Jun 25, 2024
3d8d673
chore: lint fix
haakonflatval-cognite Jun 25, 2024
317fb49
chore: refactor/"simplify" RevealButtons
haakonflatval-cognite Jun 25, 2024
bd88c14
chore: don't send undefined as argument to overlay constructor
haakonflatval-cognite Jun 25, 2024
2318d22
chore: never reconstruct command object in command buttons
haakonflatval-cognite Jun 25, 2024
a6b88ed
Merge branch 'master' into hflatval/observations-ui
haakonflatval-cognite Jun 25, 2024
06811ee
chore: remove unnecessary domainObject functions
haakonflatval-cognite Jun 26, 2024
6835aaa
chore: handle selection and view updates outside tool
haakonflatval-cognite Jun 26, 2024
b700574
chore: update reveal version
haakonflatval-cognite Jun 26, 2024
32d64dc
chore: move function
haakonflatval-cognite Jun 26, 2024
493ffb0
feat(react-components): Make sure stories always are initialized with…
haakonflatval-cognite Jun 26, 2024
0fcba57
chore: lint fix
haakonflatval-cognite Jun 26, 2024
d43d2b9
Merge master into hflatval/observations-ui
cognite-bulldozer[bot] Jun 26, 2024
3cc36d4
chore: move requestRedraw
haakonflatval-cognite Jun 26, 2024
5550d2c
Merge refs/heads/master into hflatval/observations-ui
cognite-bulldozer[bot] Jun 26, 2024
9849f8b
Merge refs/heads/master into hflatval/observations-ui
cognite-bulldozer[bot] Jun 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions react-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
},
"peerDependencies": {
"@cognite/cogs.js": ">=9.84.3",
"@cognite/reveal": "4.14.7",
"@cognite/reveal": "4.15.0",
"react": ">=18",
"react-dom": ">=18",
"styled-components": ">=5"
Expand All @@ -44,7 +44,7 @@
"@cognite/cdf-i18n-utils": "^0.7.5",
"@cognite/cdf-utilities": "^3.6.0",
"@cognite/cogs.js": "^9.84.3",
"@cognite/reveal": "^4.14.7",
"@cognite/reveal": "^4.15.0",
"@cognite/sdk": "^9.13.0",
"@playwright/test": "^1.43.1",
"@storybook/addon-essentials": "^8.0.9",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export abstract class BaseTool extends RenderTargetCommand {
if (!(customObject instanceof ThreeView)) {
return false;
}
return domainObjectPredicate(customObject.domainObject);
return domainObjectPredicate(customObject.domainObject as DomainObject);
haakonflatval-cognite marked this conversation as resolved.
Show resolved Hide resolved
};
return await viewer.getAnyIntersectionFromPixel(point, { predicate });
}
Expand Down
8 changes: 4 additions & 4 deletions react-components/src/architecture/base/views/BaseView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import { type DomainObjectChange } from '../domainObjectsHelpers/DomainObjectCha
* Represents the observer in the Observer pattern
* It provides common functionality for all types of views.
*/
export abstract class BaseView {
export abstract class BaseView<DomainObjectType extends DomainObject = DomainObject> {
// ==================================================
// INSTANCE FIELDS
// ==================================================

private _domainObject: DomainObject | undefined = undefined;
private _domainObject: DomainObjectType | undefined = undefined;

// ==================================================
// INSTANCE PROPERTIES
Expand All @@ -24,7 +24,7 @@ export abstract class BaseView {
return this._domainObject !== undefined;
}

public get domainObject(): DomainObject {
public get domainObject(): DomainObjectType {
if (this._domainObject === undefined) {
throw Error('The DomainObject is missing in the view');
}
Expand Down Expand Up @@ -92,7 +92,7 @@ export abstract class BaseView {
// INSTANCE METHODS
// ==================================================

public setDomainObject(domainObject: DomainObject): void {
public setDomainObject(domainObject: DomainObjectType): void {
this._domainObject = domainObject;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from '@cognite/reveal';
import { type DomainObjectIntersection } from '../domainObjectsHelpers/DomainObjectIntersection';
import { VisualDomainObject } from '../domainObjects/VisualDomainObject';
import { type DomainObject } from '../domainObjects/DomainObject';

/**
* Represents an abstract class for a Three.js view that renders an Object3D.
Expand All @@ -24,7 +25,10 @@ import { VisualDomainObject } from '../domainObjects/VisualDomainObject';
* - calculateBoundingBox() to calculate the bounding box if you don not relay on three.js.
*/

export abstract class GroupThreeView extends ThreeView implements ICustomObject {
export abstract class GroupThreeView<DomainObjectType extends DomainObject = DomainObject>
extends ThreeView<DomainObjectType>
implements ICustomObject
{
// ==================================================
// INSTANCE FIELDS
// ==================================================
Expand Down
6 changes: 4 additions & 2 deletions react-components/src/architecture/base/views/ThreeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import { type PerspectiveCamera, type Box3 } from 'three';
* It can for instance be a view that changes something on another view, dor instance texture on a surface or whatever.
* I just wanted to make it ready for some corner cases I have seen during a long time as 3D developer.
*/
export abstract class ThreeView extends BaseView {
export abstract class ThreeView<
DomainObjectType extends DomainObject = DomainObject
> extends BaseView<DomainObjectType> {
// ==================================================
// INSTANCE FIELDS
// ==================================================
Expand Down Expand Up @@ -112,7 +114,7 @@ export abstract class ThreeView extends BaseView {
this._boundingBox = undefined;
}

public attach(domainObject: DomainObject, renderTarget: RevealRenderTarget): void {
public attach(domainObject: DomainObjectType, renderTarget: RevealRenderTarget): void {
super.setDomainObject(domainObject);
this._renderTarget = renderTarget;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*!
* Copyright 2024 Cognite AS
*/
import { type Overlay3DCollection } from '@cognite/reveal';
import { type Observation } from './models';
import { VisualDomainObject } from '../../base/domainObjects/VisualDomainObject';
import { type ThreeView } from '../../base/views/ThreeView';
import { ObservationsView } from './ObservationsView';
import { type TranslateKey } from '../../base/utilities/TranslateKey';

export class ObservationsDomainObject extends VisualDomainObject {
private readonly _collection: Overlay3DCollection<Observation>;

public override get typeName(): TranslateKey {
return { fallback: ObservationsDomainObject.name };
}

constructor(collection: Overlay3DCollection<Observation>) {
super();
this._collection = collection;
}

public get overlayCollection(): Overlay3DCollection<Observation> {
return this._collection;
}

protected override createThreeView(): ThreeView<ObservationsDomainObject> | undefined {
return new ObservationsView(this._collection);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*!
* Copyright 2024 Cognite AS
*/
import { type IconType } from '@cognite/cogs.js';
import { type TranslateKey } from '../../base/utilities/TranslateKey';
import { type FdmSDK, type InstanceFilter } from '../../../utilities/FdmSDK';
import { OBSERVATION_SOURCE, type Observation, type ObservationProperties } from './models';
import { ObservationsDomainObject } from './ObservationsDomainObject';
import { Color, Vector3 } from 'three';
import { CDF_TO_VIEWER_TRANSFORMATION, type Overlay3D, Overlay3DCollection } from '@cognite/reveal';
import { NavigationTool } from '../../base/concreteCommands/NavigationTool';
import { type RevealRenderTarget } from '../../base/renderTarget/RevealRenderTarget';

const DEFAULT_OVERLAY_COLOR = new Color('lightblue');
const SELECTED_OVERLAY_COLOR = new Color('red');
haakonflatval-cognite marked this conversation as resolved.
Show resolved Hide resolved

export class ObservationsTool extends NavigationTool {
// private _domainObjects: ObservationsDomainObject[] = [];
haakonflatval-cognite marked this conversation as resolved.
Show resolved Hide resolved
private readonly _domainObjectPromise: Promise<ObservationsDomainObject>;

private _selectedOverlay: Overlay3D<Observation> | undefined;
haakonflatval-cognite marked this conversation as resolved.
Show resolved Hide resolved

constructor(fdmSdk: FdmSDK) {
haakonflatval-cognite marked this conversation as resolved.
Show resolved Hide resolved
super();
this._domainObjectPromise = fetchObservations(fdmSdk).then(
async (observations) => await this.initializeDomainObjects(observations)
);
}

private async initializeDomainObjects(
observations: Observation[]
): Promise<ObservationsDomainObject> {
const observationOverlays = observations.map((observation) => {
const position = new Vector3(
observation.properties.positionX,
observation.properties.positionY,
observation.properties.positionZ
).applyMatrix4(CDF_TO_VIEWER_TRANSFORMATION);

return {
position,
content: observation
};
});

const collection = new Overlay3DCollection<Observation>(observationOverlays, {
defaultOverlayColor: DEFAULT_OVERLAY_COLOR
});

const observationDomainObject = new ObservationsDomainObject(collection);

return observationDomainObject;
}

public override get icon(): IconType {
haakonflatval-cognite marked this conversation as resolved.
Show resolved Hide resolved
return 'Location';
}

public override get tooltip(): TranslateKey {
return { fallback: 'Show observations' };
}

public override attach(renderTarget: RevealRenderTarget): void {
super.attach(renderTarget);
void this._domainObjectPromise.then((domainObject) => {
renderTarget.rootDomainObject.addChildInteractive(domainObject);
});
}

public override onActivate(): void {
void this._domainObjectPromise.then((domainObject) =>
domainObject.setVisibleInteractive(true, this.renderTarget)
);
}

public override onDeactivate(): void {
void this._domainObjectPromise.then((domainObject) => {
domainObject.setVisibleInteractive(false, this.renderTarget);
});
}

public override async onClick(event: PointerEvent): Promise<void> {
const domainObject = await this._domainObjectPromise;
this._selectedOverlay?.setColor(DEFAULT_OVERLAY_COLOR);

const normalizedCoord = this.getNormalizedPixelCoordinates(event);
haakonflatval-cognite marked this conversation as resolved.
Show resolved Hide resolved
const intersection = domainObject.overlayCollection.intersectOverlays(
normalizedCoord,
this.renderTarget.camera
);

if (intersection !== undefined) {
this.handleIntersectedOverlay(intersection);
} else {
this._selectedOverlay = undefined;
}

this.renderTarget.viewer.requestRedraw();
haakonflatval-cognite marked this conversation as resolved.
Show resolved Hide resolved
}

private handleIntersectedOverlay(overlay: Overlay3D<Observation>): void {
overlay.setColor(SELECTED_OVERLAY_COLOR);
this.renderTarget.viewer.requestRedraw();
this._selectedOverlay = overlay;
}
}

const observationsFilter: InstanceFilter = {};

async function fetchObservations(fdmSdk: FdmSDK): Promise<Observation[]> {
const observationResult = await fdmSdk.filterAllInstances<ObservationProperties>(
observationsFilter,
'node',
OBSERVATION_SOURCE
);

return observationResult.instances;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*!
* Copyright 2024 Cognite AS
*/
import { Box3 } from 'three';
import { type ObservationsDomainObject } from './ObservationsDomainObject';
import { GroupThreeView } from '../../base/views/GroupThreeView';
import { type Overlay3DCollection } from '@cognite/reveal';
import { type Observation } from './models';

export class ObservationsView extends GroupThreeView<ObservationsDomainObject> {
private readonly _collection: Overlay3DCollection<Observation>;
haakonflatval-cognite marked this conversation as resolved.
Show resolved Hide resolved

constructor(collection: Overlay3DCollection<Observation>) {
super();
this._collection = collection;
}

protected override calculateBoundingBox(): Box3 {
return this._collection
.getOverlays()
.reduce((box, overlay) => box.expandByPoint(overlay.getPosition()), new Box3());
}

protected override addChildren(): void {
this.addChild(this._collection);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*!
* Copyright 2024 Cognite AS
*/
import { type DmsUniqueIdentifier, type FdmNode, type Source } from '../../../utilities/FdmSDK';

export type ObservationProperties = {
// "ID as the node appears in the Source system"
sourceId: string;
// "Name of the source system node comes from"
source: string;
// "Title or name of the node"
title: string;
// "Long description of the node"
description: string;
// "Text based labels for generic use"
labels: string[];
// "Visibility of node (PUBLIC, PRIVATE, PROTECTED)"
visibility: string;
// "Who created this node?"
createdBy: string;
// "Who was the last person to update this node?"
updatedBy: string;
// "Is this item archived, and therefore hidden from most UIs?"
isArchived: boolean;
// "The status of the observation (draft, completed, sent)"
status: string;
// "External ID of the associated CDF Asset"
asset: DmsUniqueIdentifier;
// "List of associated files"
files: DmsUniqueIdentifier[];
// "description of how the observation was troubleshooted"
troubleshooting: string;
// "Priority of the observation (Urgent, High ...)"
priority: string;
// "The observation type (Malfunction report, Maintenance request, etc.)"
type: string;
// "3D position"
positionX: number;
positionY: number;
positionZ: number;
// "Comments"
comments: [CommentProperties];
};

export type CommentProperties = {
createdBy: string;
text: string;
};

export type Observation = FdmNode<ObservationProperties>;

export const OBSERVATION_SOURCE: Source = {
type: 'view',
version: '3c207ca2355dbb',
externalId: 'Observation',
space: 'observations'
};
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,14 @@ export const CommandButtons = ({
);
};

export const CreateCommandButton = (command: BaseCommand, isHorizontal = false): ReactElement => {
export const CommandButtonFromCommand = ({
commandConstructor,
isHorizontal = false
}: {
commandConstructor: () => BaseCommand;
isHorizontal?: boolean;
}): ReactElement => {
const command = useMemo(commandConstructor, [commandConstructor]);
return <CommandButton command={command} isHorizontal={isHorizontal} />;
};

Expand Down
Loading
Loading