diff --git a/react-components/package.json b/react-components/package.json index 3b15d39c271..29b0b98a2fe 100644 --- a/react-components/package.json +++ b/react-components/package.json @@ -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" @@ -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", diff --git a/react-components/src/architecture/base/commands/BaseCommand.ts b/react-components/src/architecture/base/commands/BaseCommand.ts index f78a685e8f6..274c0d8d200 100644 --- a/react-components/src/architecture/base/commands/BaseCommand.ts +++ b/react-components/src/architecture/base/commands/BaseCommand.ts @@ -2,7 +2,7 @@ * Copyright 2024 Cognite AS */ -import { type TranslateKey } from '../utilities/TranslateKey'; +import { type TranslateDelegate, type TranslateKey } from '../utilities/TranslateKey'; import { clear, remove } from '../utilities/extensions/arrayExtensions'; type UpdateDelegate = (command: BaseCommand) => void; @@ -44,7 +44,7 @@ export abstract class BaseCommand { // ================================================= public get name(): string { - return this.tooltip.fallback ?? this.tooltip.key; + return this.tooltip.fallback; } public get shortCutKey(): string | undefined { @@ -55,8 +55,8 @@ export abstract class BaseCommand { return { fallback: '' }; } - public get icon(): string { - return 'Unknown'; + public get icon(): string | undefined { + return undefined; // Means no icon } public get buttonType(): string { @@ -128,4 +128,12 @@ export abstract class BaseCommand { listener(this); } } + + public getLabel(translate: TranslateDelegate): string { + const { key, fallback } = this.tooltip; + if (key === undefined) { + return fallback; + } + return translate(key, fallback); + } } diff --git a/react-components/src/architecture/base/commands/BaseOptionCommand.ts b/react-components/src/architecture/base/commands/BaseOptionCommand.ts new file mode 100644 index 00000000000..7aaed2c49c7 --- /dev/null +++ b/react-components/src/architecture/base/commands/BaseOptionCommand.ts @@ -0,0 +1,48 @@ +/*! + * Copyright 2024 Cognite AS + */ + +import { type RevealRenderTarget } from '../renderTarget/RevealRenderTarget'; +import { BaseCommand } from './BaseCommand'; +import { RenderTargetCommand } from './RenderTargetCommand'; + +/** + * Base class for all command and tools. These are object that can do a + * user interaction with the system. It also have enough information to + * generate the UI for the command. + */ + +export abstract class BaseOptionCommand extends BaseCommand { + private _options: BaseCommand[] | undefined = undefined; + + // ================================================== + // VIRTUAL METHODS + // ================================================== + + public createOptions(): BaseCommand[] { + return []; // Override this to add options + } + + // ================================================== + // INSTANCE METHODS + // ================================================== + + public getOrCreateOptions(renderTarget: RevealRenderTarget): BaseCommand[] { + if (this._options === undefined) { + this._options = this.createOptions(); + for (const option of this._options) { + if (option instanceof RenderTargetCommand) { + option.attach(renderTarget); + } + } + } + return this._options; + } + + public get selectedOption(): BaseCommand | undefined { + if (this._options === undefined) { + return undefined; + } + return this._options.find((option) => option.isChecked); + } +} diff --git a/react-components/src/architecture/base/concreteCommands/KeyboardSpeedCommand.ts b/react-components/src/architecture/base/concreteCommands/KeyboardSpeedCommand.ts new file mode 100644 index 00000000000..88915de486d --- /dev/null +++ b/react-components/src/architecture/base/concreteCommands/KeyboardSpeedCommand.ts @@ -0,0 +1,51 @@ +/*! + * Copyright 2024 Cognite AS + */ + +import { type BaseCommand } from '../commands/BaseCommand'; +import { BaseOptionCommand } from '../commands/BaseOptionCommand'; +import { RenderTargetCommand } from '../commands/RenderTargetCommand'; +import { type TranslateKey } from '../utilities/TranslateKey'; + +const KEYBOARD_SPEED_VALUES = [0.5, 1, 2, 5, 10, 20]; + +export class KeyboardSpeedCommand extends BaseOptionCommand { + // ================================================== + // OVERRIDES + // ================================================== + + public override get tooltip(): TranslateKey { + return { key: 'FLY_SPEED', fallback: 'Set fly speed on the camera' }; + } + + public override createOptions(): BaseCommand[] { + const options: BaseCommand[] = []; + for (const value of KEYBOARD_SPEED_VALUES) { + options.push(new OptionCommand(value)); + } + return options; + } +} + +// Note: This is not exported, as it is only used internally +class OptionCommand extends RenderTargetCommand { + private readonly _value; + + public constructor(value: number) { + super(); + this._value = value; + } + + public override get tooltip(): TranslateKey { + return { fallback: `${this._value.toString()}x` }; + } + + public override get isChecked(): boolean { + return this._value === this.renderTarget.flexibleCameraManager.options.keyboardSpeed; + } + + public override invokeCore(): boolean { + this.renderTarget.flexibleCameraManager.options.keyboardSpeed = this._value; + return true; + } +} diff --git a/react-components/src/architecture/base/domainObjects/DomainObject.ts b/react-components/src/architecture/base/domainObjects/DomainObject.ts index 4b93b116463..520e83db5cf 100644 --- a/react-components/src/architecture/base/domainObjects/DomainObject.ts +++ b/react-components/src/architecture/base/domainObjects/DomainObject.ts @@ -323,8 +323,8 @@ export abstract class DomainObject { // VIRTUAL METHODS: Others // ================================================== - public get icon(): string { - return 'Unknown'; + public get icon(): string | undefined { + return undefined; } /** diff --git a/react-components/src/architecture/concrete/config/StoryBookConfig.ts b/react-components/src/architecture/concrete/config/StoryBookConfig.ts index adee0bac141..ae62b078558 100644 --- a/react-components/src/architecture/concrete/config/StoryBookConfig.ts +++ b/react-components/src/architecture/concrete/config/StoryBookConfig.ts @@ -19,6 +19,7 @@ import { type BaseTool } from '../../base/commands/BaseTool'; import { ToggleMetricUnitsCommand } from '../../base/concreteCommands/ToggleMetricUnitsCommand'; import { MeasurementTool } from '../measurements/MeasurementTool'; import { ClipTool } from '../clipping/ClipTool'; +import { KeyboardSpeedCommand } from '../../base/concreteCommands/KeyboardSpeedCommand'; export class StoryBookConfig extends BaseRevealConfig { // ================================================== @@ -37,6 +38,7 @@ export class StoryBookConfig extends BaseRevealConfig { new FitViewCommand(), new SetAxisVisibleCommand(), new ToggleMetricUnitsCommand(), + new KeyboardSpeedCommand(), undefined, new ExampleTool(), new MeasurementTool(), diff --git a/react-components/src/components/Architecture/CommandButton.tsx b/react-components/src/components/Architecture/CommandButton.tsx index 4d25779a93b..089da5315fc 100644 --- a/react-components/src/components/Architecture/CommandButton.tsx +++ b/react-components/src/components/Architecture/CommandButton.tsx @@ -2,134 +2,65 @@ * Copyright 2023 Cognite AS */ -import { type ReactElement, useState, useEffect, useMemo } from 'react'; +import { type ReactElement, useState, useEffect, useMemo, useCallback } from 'react'; import { useRenderTarget } from '../RevealCanvas/ViewerContext'; -import { Button, Tooltip as CogsTooltip, Divider, type IconType } from '@cognite/cogs.js'; +import { Button, Tooltip as CogsTooltip, type IconType } from '@cognite/cogs.js'; import { useTranslation } from '../i18n/I18n'; import { type BaseCommand } from '../../architecture/base/commands/BaseCommand'; -import { type RevealRenderTarget } from '../../architecture/base/renderTarget/RevealRenderTarget'; -import { RenderTargetCommand } from '../../architecture/base/commands/RenderTargetCommand'; - -export const CommandButtons = ({ - commands, - isHorizontal = false -}: { - commands: Array; - isHorizontal: boolean; -}): ReactElement => { - return ( - <> - {commands.map( - (command, index): ReactElement => ( - - ) - )} - - ); -}; - -export const CreateCommandButton = (command: BaseCommand, isHorizontal = false): ReactElement => { - return ; -}; +import { getButtonType, getDefaultCommand, getIcon, getTooltipPlacement } from './utilities'; export const CommandButton = ({ - command, + inputCommand, isHorizontal = false }: { - command: BaseCommand; + inputCommand: BaseCommand; isHorizontal: boolean; }): ReactElement => { const renderTarget = useRenderTarget(); const { t } = useTranslation(); - const newCommand = useMemo(() => getDefaultCommand(command, renderTarget), []); + const command = useMemo(() => getDefaultCommand(inputCommand, renderTarget), []); const [isChecked, setChecked] = useState(false); const [isEnabled, setEnabled] = useState(true); const [isVisible, setVisible] = useState(true); const [uniqueId, setUniqueId] = useState(0); - const [icon, setIcon] = useState('Copy'); + const [icon, setIcon] = useState(undefined); + + const update = useCallback((command: BaseCommand) => { + setChecked(command.isChecked); + setEnabled(command.isEnabled); + setVisible(command.isVisible); + setUniqueId(command.uniqueId); + setIcon(getIcon(command)); + }, []); useEffect(() => { - function update(command: BaseCommand): void { - setChecked(command.isChecked); - setEnabled(command.isEnabled); - setVisible(command.isVisible); - setUniqueId(command.uniqueId); - setIcon(command.icon as IconType); - } - update(newCommand); - newCommand.addEventListener(update); + update(command); + command.addEventListener(update); return () => { - newCommand.removeEventListener(update); + command.removeEventListener(update); }; - }, [newCommand.isEnabled, newCommand.isChecked, newCommand.isVisible]); + }, [command.isEnabled, command.isChecked, command.isVisible]); if (!isVisible) { return <>; } - const placement = isHorizontal ? 'top' : 'right'; - const { key, fallback } = newCommand.tooltip; - // This was the only way it went through compiler: (more button types will be added in the future) - const type = newCommand.buttonType; - if (type !== 'ghost' && type !== 'ghost-destructive' && type !== 'primary') { - return <>; - } - const text = key === undefined ? fallback : t(key, fallback); + const placement = getTooltipPlacement(isHorizontal); + const tooltip = command.getLabel(t); return ( - +