Skip to content

Commit

Permalink
feat(react-components): poi architecture changes (#4859)
Browse files Browse the repository at this point in the history
* chore: implement POI ADS provider

* feat(react-components): PoI-related architecture changes

* chore: add missing export

* chore: lint fix

* chore: revert PoI-changes (not relevant in this PR)

* chore: typing fix

* chore: modify BaseInputCommand to be a base class

* chore: fix according to feedback

* chore: lint fix, fix compiler errors

* chore: add missing export

* chore: accept undefined subject in update hooks
  • Loading branch information
haakonflatval-cognite authored Nov 13, 2024
1 parent f5ac8c8 commit fcd8766
Show file tree
Hide file tree
Showing 21 changed files with 459 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { type BaseDragger } from '../domainObjectsHelpers/BaseDragger';
import { VisualDomainObject } from '../domainObjects/VisualDomainObject';
import { type AnyIntersection } from '@cognite/reveal';
import { DomainObjectPanelUpdater } from '../reactUpdaters/DomainObjectPanelUpdater';
import { type CommandsController } from '../renderTarget/CommandsController';
import { PrimitiveEditTool } from '../../concrete/primitives/tools/PrimitiveEditTool';

/**
* The `BaseEditTool` class is an abstract class that extends the `NavigationTool` class.
Expand Down Expand Up @@ -207,3 +209,7 @@ export abstract class BaseEditTool extends NavigationTool {
return domainObject;
}
}

export function isActiveEditTool(commandController: CommandsController): boolean {
return commandController.activeTool instanceof PrimitiveEditTool;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*!
* Copyright 2024 Cognite AS
*/
import { type TranslateDelegate, type TranslateKey } from '../utilities/TranslateKey';
import { RenderTargetCommand } from './RenderTargetCommand';

export abstract class BaseInputCommand extends RenderTargetCommand {
protected _placeholder?: TranslateKey;
protected _content?: string;
protected _okButtonLabel?: TranslateKey;

protected _onFinish?: () => void;
protected _onCancel?: () => void;

public getCancelButtonLabel(_t: TranslateDelegate): string | undefined {
return undefined;
}

public abstract getPostButtonLabel(t: TranslateDelegate): string | undefined;

public abstract getPlaceholder(t: TranslateDelegate): string | undefined;

public get onFinish(): (() => void) | undefined {
return this._onFinish;
}

public set onFinish(onFinish: () => void) {
this._onFinish = onFinish;
}

public set onCancel(onCancel: () => void) {
this._onCancel = onCancel;
}

public get onCancel(): (() => void) | undefined {
return this._onCancel;
}

invokeWithContent(content: string): boolean {
this._content = content;

const invokeResult = this.invoke();
this._onFinish?.();
return invokeResult;
}
}
51 changes: 49 additions & 2 deletions react-components/src/architecture/base/commands/BaseTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Copyright 2024 Cognite AS
*/

import { type Ray, Raycaster, type Vector2 } from 'three';
import { MOUSE, type Ray, Raycaster, Vector2, type Vector3 } from 'three';
import { RenderTargetCommand } from './RenderTargetCommand';
import {
type CustomObjectIntersection,
Expand All @@ -21,6 +21,16 @@ import { PopupStyle } from '../domainObjectsHelpers/PopupStyle';
import { ThreeView } from '../views/ThreeView';
import { UndoManager } from '../undo/UndoManager';
import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
import { ContextMenuUpdater } from '../reactUpdaters/ContextMenuUpdater';

/**
* AnchorDialog
*/
export type AnchoredDialogContent = {
position: Vector3;
onCloseCallback: () => void;
contentCommands: BaseCommand[];
};

/**
* Base class for interactions in the 3D viewer
Expand Down Expand Up @@ -58,6 +68,10 @@ export abstract class BaseTool extends RenderTargetCommand {
return 'default';
}

public getAnchoredDialogContent(): AnchoredDialogContent | undefined {
return undefined;
}

public getToolbar(): Array<BaseCommand | undefined> {
return []; // Override this to add extra buttons to a separate toolbar
}
Expand Down Expand Up @@ -94,7 +108,13 @@ export abstract class BaseTool extends RenderTargetCommand {
// Debounce version. Use this when doing intersection with CAD models and other large models
}

public async onClick(_event: PointerEvent): Promise<void> {
public async onClick(event: PointerEvent): Promise<void> {
if (event.button === (MOUSE.RIGHT as number)) {
await this.openContextMenu(event);
} else if (this.isContextMenuOpen()) {
await this.closeContextMenu();
}

await Promise.resolve();
}

Expand Down Expand Up @@ -238,4 +258,31 @@ export abstract class BaseTool extends RenderTargetCommand {
public setDefaultCursor(): void {
this.renderTarget.cursor = this.defaultCursor;
}

private async openContextMenu(event: PointerEvent): Promise<void> {
const intersection = await this.getIntersection(event);

if (this._renderTarget === undefined) {
return;
}

this._renderTarget.contextMenuController.contextMenuPositionData = {
position: new Vector2(event.layerX, event.layerY),
intersection
};

ContextMenuUpdater.update();
}

private async closeContextMenu(): Promise<void> {
if (this._renderTarget === undefined) {
return;
}

this._renderTarget.contextMenuController.contextMenuPositionData = undefined;
}

private isContextMenuOpen(): boolean {
return this._renderTarget?.contextMenuController.contextMenuPositionData !== undefined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export abstract class RenderTargetCommand extends BaseCommand {

public get renderTarget(): RevealRenderTarget {
if (this._renderTarget === undefined) {
throw new Error('Render target is not set');
throw new Error('Render target is not set. Have you called attach() on this command?');
}
return this._renderTarget;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ export class NavigationTool extends BaseTool {
}
return isEntered;
});
if (!(await promise)) {
await this.cameraManager.onClick(event);
if (await promise) {
return;
}
await super.onClick(event);
await this.cameraManager.onClick(event);
}

public override async onDoubleClick(event: PointerEvent): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*!
* Copyright 2024 Cognite AS
*/

export type SetCounterDelegate = (counter: number) => void;

export class AnchoredDialogUpdater {
// ==================================================
// STATIC FIELDS
// ==================================================

private static _setCounter: SetCounterDelegate | undefined = undefined;
private static _counter = 0;

// ==================================================
// STATIC METHODS
// ==================================================

public static setCounterDelegate(value: SetCounterDelegate | undefined): void {
this._setCounter = value;
}

public static update(): void {
// Increment the counter, so the state change in React and force a redraw each time the active tool changes
// The reason for solution it that I only want to store the active tool at one single location, since this gives a more
// stable code, and never goes out of sync.
// React get the active tool by: renderTarget.commandsController.activeTool;
if (this._setCounter === undefined) {
return;
}
this._counter++;
this._setCounter(this._counter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*!
* Copyright 2024 Cognite AS
*/

export type SetCounterDelegate = (counter: number) => void;

export class ContextMenuUpdater {
// ==================================================
// STATIC FIELDS
// ==================================================

private static _setCounter: SetCounterDelegate | undefined = undefined;
private static _counter = 0;

// ==================================================
// STATIC METHODS
// ==================================================

public static setCounterDelegate(value: SetCounterDelegate | undefined): void {
this._setCounter = value;
}

public static update(): void {
// Increment the counter, so the state change in React and force a redraw each time the active tool changes
// The reason for solution it that I only want to store the active tool at one single location, since this gives a more
// stable code, and never goes out of sync.
// React get the active tool by: renderTarget.commandsController.activeTool;
if (this._setCounter === undefined) {
return;
}
this._counter++;
this._setCounter(this._counter);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,16 @@ export class CommandsController extends PointerEvents {
return this.activateDefaultTool();
}

public getToolByType<T extends BaseTool>(classType: Class<T>): T | undefined {
for (const tool of this._commands) {
if (isInstanceOf(tool, classType)) {
return tool;
}
}

return undefined;
}

public setActiveToolByType<T extends BaseTool>(classType: Class<T>): boolean {
for (const tool of this._commands) {
if (isInstanceOf(tool, classType)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*!
* Copyright 2024 Cognite AS
*/
import { type AnyIntersection } from '@cognite/reveal';
import { ContextMenuUpdater } from '../reactUpdaters/ContextMenuUpdater';
import { type Vector2 } from 'three';

export type ContextMenuData = {
position: Vector2;
intersection: AnyIntersection | undefined;
};

export class ContextMenuController {
private _contextMenuPosition: ContextMenuData | undefined = undefined;

public get contextMenuPositionData(): ContextMenuData | undefined {
return this._contextMenuPosition;
}

public set contextMenuPositionData(data: ContextMenuData | undefined) {
this._contextMenuPosition = data;
ContextMenuUpdater.update();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { getBoundingBoxFromPlanes } from '../utilities/geometry/getBoundingBoxFr
import { Changes } from '../domainObjectsHelpers/Changes';
import { type CogniteClient } from '@cognite/sdk/dist/src';
import { type BaseTool } from '../commands/BaseTool';
import { ContextMenuController } from './ContextMenuController';

const DIRECTIONAL_LIGHT_NAME = 'DirectionalLight';

Expand All @@ -46,6 +47,7 @@ export class RevealRenderTarget {
private readonly _viewer: Cognite3DViewer;
private readonly _commandsController: CommandsController;
private readonly _rootDomainObject: RootDomainObject;
private readonly _contextmenuController: ContextMenuController;
private _ambientLight: AmbientLight | undefined;
private _directionalLight: DirectionalLight | undefined;
private _clippedBoundingBox: Box3 | undefined;
Expand All @@ -69,6 +71,7 @@ export class RevealRenderTarget {
}
this._commandsController = new CommandsController(this.domElement);
this._commandsController.addEventListeners();
this._contextmenuController = new ContextMenuController();
this._rootDomainObject = new RootDomainObject(this, sdk);

this.initializeLights();
Expand Down Expand Up @@ -105,6 +108,10 @@ export class RevealRenderTarget {
return this._commandsController;
}

public get contextMenuController(): ContextMenuController {
return this._contextmenuController;
}

public get cursor(): string {
return this.domElement.style.cursor;
}
Expand Down
1 change: 1 addition & 0 deletions react-components/src/architecture/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export { VisualDomainObject } from './base/domainObjects/VisualDomainObject';

export { BaseRevealConfig } from './base/renderTarget/BaseRevealConfig';
export { CommandsController } from './base/renderTarget/CommandsController';
export { type ContextMenuData } from './base/renderTarget/ContextMenuController';
export { DefaultRevealConfig } from './base/renderTarget/DefaultRevealConfig';
export { RevealRenderTarget } from './base/renderTarget/RevealRenderTarget';
export { UnitSystem } from './base/renderTarget/UnitSystem';
Expand Down
45 changes: 45 additions & 0 deletions react-components/src/components/Architecture/AnchoredDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*!
* Copyright 2024 Cognite AS
*/
import { type ReactNode, useMemo, useState } from 'react';
import { useRenderTarget } from '../RevealCanvas';
import { AnchoredDialogUpdater } from '../../architecture/base/reactUpdaters/AnchoredDialogUpdater';
import { ViewerAnchor } from '../ViewerAnchor/ViewerAnchor';
import { Menu } from '@cognite/cogs.js';
import { createButton } from './CommandButtons';
import styled from 'styled-components';
import { withSuppressRevealEvents } from '../../higher-order-components/withSuppressRevealEvents';

export const AnchoredDialog = (): ReactNode => {
const [activeToolUpdate, setActiveToolUpdater] = useState<number>(0);

AnchoredDialogUpdater.setCounterDelegate(setActiveToolUpdater);

const renderTarget = useRenderTarget();
if (renderTarget === undefined) {
return <></>;
}
const activeTool = renderTarget.commandsController.activeTool;
if (activeTool === undefined) {
return <></>;
}

const dialogContent = useMemo(() => activeTool.getAnchoredDialogContent(), [activeToolUpdate]);
const isSomeEnabled = dialogContent?.contentCommands.some((command) => command.isEnabled);

if (dialogContent === undefined || isSomeEnabled !== true) {
return undefined;
}

return (
<ViewerAnchor position={dialogContent?.position}>
<SuppressedMenu>
{dialogContent.contentCommands.map((command) => createButton(command, 'right'))}
</SuppressedMenu>
</ViewerAnchor>
);
};

const SuppressedMenu = withSuppressRevealEvents(styled(Menu)`
background-color: white;
`);
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { SectionCommand } from '../../architecture/base/commands/SectionCommand'
import { type PlacementType } from './types';
import { type ButtonProp } from './RevealButtons';
import { getDividerDirection } from './utilities';
import { InputField } from './InputField';
import { BaseInputCommand } from '../../architecture/base/commands/BaseInputCommand';

export function createButton(command: BaseCommand, placement: PlacementType): ReactElement {
if (command instanceof BaseFilterCommand) {
Expand All @@ -36,6 +38,11 @@ export function createButton(command: BaseCommand, placement: PlacementType): Re
return <></>;
}
}

if (command instanceof BaseInputCommand) {
return <InputField key={command.uniqueId} inputCommand={command} placement={placement} />;
}

return <CommandButton inputCommand={command} placement={placement} />;
}

Expand Down
Loading

0 comments on commit fcd8766

Please sign in to comment.