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): create and delete observations #4658

Merged
merged 20 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b61bb95
feat(react-components): create and delete observations
haakonflatval-cognite Jul 10, 2024
4641907
chore: lint fix
haakonflatval-cognite Jul 10, 2024
142fbc4
chore: Make source argument required
haakonflatval-cognite Jul 11, 2024
96ca80c
chore: rewrite according to review
haakonflatval-cognite Jul 12, 2024
ff01008
chore: lint fix
haakonflatval-cognite Jul 12, 2024
e086ad7
fix: logic related to removing/adding pending/created observations
haakonflatval-cognite Jul 15, 2024
7b85b26
chore: don't select interactive
haakonflatval-cognite Jul 15, 2024
d6e7034
chore: simplify pending-queries
haakonflatval-cognite Jul 15, 2024
b578650
feat: handle clipping
haakonflatval-cognite Jul 15, 2024
c82151e
chore: simplify overlay selection code
haakonflatval-cognite Jul 15, 2024
ee20745
chore: only save through SaveCommand
haakonflatval-cognite Jul 15, 2024
0655249
chore: small refactor, increase type-safety of intersection
haakonflatval-cognite Jul 15, 2024
6f6816b
chore: lint fix
haakonflatval-cognite Jul 15, 2024
fbf4dee
chore: revert unintended changes to util function
haakonflatval-cognite Jul 15, 2024
9bdc575
chore: don't store fdmSdk in cache
haakonflatval-cognite Jul 15, 2024
b71810f
chore: use more getters
haakonflatval-cognite Jul 16, 2024
67003be
chore: move public methods
haakonflatval-cognite Jul 16, 2024
60d4e8b
chore: simplify bounding box computation, invalidate stuff, skip clip…
haakonflatval-cognite Jul 16, 2024
0dff588
Merge branch 'master' into hflatval/observation-creation
haakonflatval-cognite Jul 16, 2024
40d357a
Merge branch 'master' into hflatval/observation-creation
haakonflatval-cognite Jul 16, 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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class Changes {
public static readonly expanded: symbol = Symbol('expanded');
public static readonly selected: symbol = Symbol('selected');
public static readonly focus: symbol = Symbol('focus');
public static readonly clipping: symbol = Symbol('visibleState');
public static readonly clipping: symbol = Symbol('clipping');

// Domain object Fields changed
public static readonly naming: symbol = Symbol('naming');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { ToggleMetricUnitsCommand } from '../../base/concreteCommands/ToggleMetr
import { MeasurementTool } from '../measurements/MeasurementTool';
import { ClipTool } from '../clipping/ClipTool';
import { KeyboardSpeedCommand } from '../../base/concreteCommands/KeyboardSpeedCommand';
import { ObservationsTool } from '../observationsDomainObject/ObservationsTool';
import { ObservationsTool } from '../observations/ObservationsTool';

export class StoryBookConfig extends BaseRevealConfig {
// ==================================================
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*!
* Copyright 2024 Cognite AS
*/
import { type IconType } from '@cognite/cogs.js';
import { type TranslateKey } from '../../base/utilities/TranslateKey';
import { ObservationsCommand } from './ObservationsCommand';

export class CreateObservationCommand extends ObservationsCommand {
public override get icon(): IconType {
return 'Plus';
}

public override get tooltip(): TranslateKey {
return { key: 'ADD_OBSERVATION', fallback: 'Add observation. Click at a point' };
}

protected override invokeCore(): boolean {
const tool = this.getTool();
if (tool === undefined) {
return false;
}

tool.setIsCreating(!tool.isCreating);

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*!
* Copyright 2024 Cognite AS
*/
import { type IconType } from '@cognite/cogs.js';
import { type TranslateKey } from '../../base/utilities/TranslateKey';
import { type ButtonType } from '../../../components/Architecture/types';
import { ObservationsCommand } from './ObservationsCommand';

export class DeleteObservationCommand extends ObservationsCommand {
public override get icon(): IconType {
return 'Delete';
}

public override get tooltip(): TranslateKey {
return { fallback: 'Delete observation' };
}

public override get buttonType(): ButtonType {
return 'ghost-destructive';
}

public override get shortCutKey(): string {
return 'DELETE';
}

public override get isEnabled(): boolean {
const observation = this.getObservationsDomainObject();

return observation?.getSelectedObservation() !== undefined;
}

protected override invokeCore(): boolean {
const observations = this.getObservationsDomainObject();
const selectedOverlay = observations?.getSelectedObservation();
if (observations === undefined || selectedOverlay === undefined) {
return false;
}

observations.removeObservation(selectedOverlay);
observations.setSelectedObservation(undefined);

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*!
* Copyright 2024 Cognite AS
*/
import { type FdmSDK } from '../../../utilities/FdmSDK';
import { type ObservationFdmNode, type ObservationProperties } from './models';

import { type Observation } from './types';
import {
createObservationInstances,
deleteObservationInstances,
fetchObservations
} from './network';
import { isDefined } from '../../../utilities/isDefined';

/**
* A cache that takes care of loading the observations, but also buffers changes to the overlays
* list when e.g. adding or removing observations
*/
export class ObservationsCache {
private readonly _loadedPromise: Promise<ObservationFdmNode[]>;
private readonly _fdmSdk: FdmSDK;

constructor(fdmSdk: FdmSDK) {
this._loadedPromise = fetchObservations(fdmSdk);
nilscognite marked this conversation as resolved.
Show resolved Hide resolved
this._fdmSdk = fdmSdk;
}

public async getFinishedOriginalLoadingPromise(): Promise<ObservationFdmNode[]> {
return await this._loadedPromise;
}

public async deleteObservations(observations: Observation[]): Promise<void> {
if (observations.length === 0) {
return;
}

const observationData = observations
.map((observation) => observation.fdmMetadata)
.filter(isDefined);

await deleteObservationInstances(this._fdmSdk, observationData);
}

public async saveObservations(
observations: ObservationProperties[]
): Promise<ObservationFdmNode[]> {
if (observations.length === 0) {
return [];
}

return await createObservationInstances(this._fdmSdk, observations);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*!
* Copyright 2024 Cognite AS
*/
import { RenderTargetCommand } from '../../base/commands/RenderTargetCommand';
import { ObservationsDomainObject } from './ObservationsDomainObject';
import { ObservationsTool } from './ObservationsTool';

export abstract class ObservationsCommand extends RenderTargetCommand {
protected getTool(): ObservationsTool | undefined {
if (this.activeTool instanceof ObservationsTool) {
return this.activeTool;
}

return undefined;
}

protected getObservationsDomainObject(): ObservationsDomainObject | undefined {
return this.rootDomainObject.getDescendantByType(ObservationsDomainObject);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*!
* Copyright 2024 Cognite AS
*/
import { VisualDomainObject } from '../../base/domainObjects/VisualDomainObject';
import { type ThreeView } from '../../base/views/ThreeView';
import { ObservationsView } from './ObservationsView';
import { type TranslateKey } from '../../base/utilities/TranslateKey';
import { type FdmSDK } from '../../../utilities/FdmSDK';
import { Changes } from '../../base/domainObjectsHelpers/Changes';
import { ObservationsCache } from './ObservationsCache';
import { PanelInfo } from '../../base/domainObjectsHelpers/PanelInfo';
import { type Observation, ObservationStatus } from './types';
import { partition, remove } from 'lodash';
import { type ObservationProperties } from './models';

export class ObservationsDomainObject extends VisualDomainObject {
private _selectedObservation: Observation | undefined;
private readonly _observationsCache: ObservationsCache;

private _observations: Observation[] = [];

constructor(fdmSdk: FdmSDK) {
haakonflatval-cognite marked this conversation as resolved.
Show resolved Hide resolved
super();

this._observationsCache = new ObservationsCache(fdmSdk);
void this._observationsCache.getFinishedOriginalLoadingPromise().then((observations) => {
this._observations = observations.map((observation) => ({
fdmMetadata: observation,
properties: observation.properties,
status: ObservationStatus.Default
}));
this.notify(Changes.geometry);
});
}

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

protected override createThreeView(): ThreeView<ObservationsDomainObject> | undefined {
return new ObservationsView();
}

public override get canBeRemoved(): boolean {
return false;
}

public override get hasPanelInfo(): boolean {
return true;
}

public override getPanelInfo(): PanelInfo | undefined {
const info = new PanelInfo();
const header = { fallback: 'Observation' };
info.setHeader(header);

info.add({ fallback: 'X', value: this._selectedObservation?.properties.positionX });
info.add({ fallback: 'Y', value: this._selectedObservation?.properties.positionY });
info.add({ fallback: 'Z', value: this._selectedObservation?.properties.positionZ });

return info;
}

public addPendingObservation(observationData: ObservationProperties): Observation {
const newObservation = {
properties: observationData,
status: ObservationStatus.PendingCreation
};

this._observations.push(newObservation);

this.notify(Changes.geometry);

return newObservation;
}

public removeObservation(observation: Observation): void {
if (observation.status === ObservationStatus.PendingCreation) {
remove(this._observations, observation);
} else if (this._observations.includes(observation)) {
observation.status = ObservationStatus.PendingDeletion;
}

this.notify(Changes.geometry);
}

public getObservations(): Observation[] {
nilscognite marked this conversation as resolved.
Show resolved Hide resolved
return this._observations;
}

public getSelectedObservation(): Observation | undefined {
haakonflatval-cognite marked this conversation as resolved.
Show resolved Hide resolved
return this._selectedObservation;
}

public async save(): Promise<void> {
if (
this._selectedObservation !== undefined &&
(this._selectedObservation.status === ObservationStatus.PendingCreation ||
this._selectedObservation.status === ObservationStatus.PendingDeletion)
) {
this.setSelectedObservation(undefined);
}

const [toRemove, toKeep] = partition(
this._observations,
(observation) => observation.status === ObservationStatus.PendingDeletion
);

const deletePromise = this._observationsCache.deleteObservations(toRemove);

const observationsToCreate = this._observations.filter(
(obs) => obs.status === ObservationStatus.PendingCreation
);
const newObservations = await this._observationsCache.saveObservations(
observationsToCreate.map((obs) => obs.properties)
);

this._observations = toKeep.concat(
newObservations.map((observation) => ({
status: ObservationStatus.Default,
fdmMetadata: observation,
properties: observation.properties
}))
);

await deletePromise;

this.notify(Changes.geometry);
}

public setSelectedObservation(observation: Observation | undefined): void {
this._selectedObservation = observation;
if (this._selectedObservation === undefined) {
haakonflatval-cognite marked this conversation as resolved.
Show resolved Hide resolved
this.setSelectedInteractive(false);
} else {
this.setSelectedInteractive(true);
}

this.notify(Changes.selected);
}

public hasPendingObservations(): boolean {
return (
this._observations.find(
(observation) => observation.status === ObservationStatus.PendingCreation
) !== undefined
);
}

public hasPendingDeletionObservations(): boolean {
return (
this._observations.find((obs) => obs.status === ObservationStatus.PendingDeletion) !==
undefined
);
}
}
Loading
Loading