Skip to content

Commit

Permalink
Add ToolUtilities to define tool icon as a React element (#1150)
Browse files Browse the repository at this point in the history
* Add ToolUtilities

* Update Tool types to include iconElement property.

* NextVersion.md

* Add note

* Extract API

* Add unit tests

* Update test

* rush change

* Update snaps
  • Loading branch information
GerardasB authored Dec 11, 2024
1 parent d5c1ed9 commit 87ca8e3
Show file tree
Hide file tree
Showing 18 changed files with 325 additions and 52 deletions.
16 changes: 15 additions & 1 deletion apps/test-app/src/frontend/appui/frontstages/MainFrontstage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ import {
BackstageAppButton,
BackstageItemUtilities,
FrontstageUtilities,
RestoreFrontstageLayoutTool,
SettingsModalFrontstage,
StageUsage,
StandardContentLayouts,
ToolbarItemUtilities,
ToolbarOrientation,
ToolbarUsage,
UiItemsProvider,
} from "@itwin/appui-react";
import {
Expand Down Expand Up @@ -46,7 +50,17 @@ createMainFrontstage.stageId = "main";
export function createMainFrontstageProvider() {
return {
id: "appui-test-app:backstageItemsProvider",
getToolbarItems: () => [getCustomViewSelectorPopupItem()],
getToolbarItems: () => [
getCustomViewSelectorPopupItem(),
ToolbarItemUtilities.createForTool(RestoreFrontstageLayoutTool, {
layouts: {
standard: {
orientation: ToolbarOrientation.Horizontal,
usage: ToolbarUsage.ContentManipulation,
},
},
}),
],
getBackstageItems: () => [
BackstageItemUtilities.createStageLauncher({
stageId: createMainFrontstage.stageId,
Expand Down
28 changes: 6 additions & 22 deletions common/api/appui-react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3665,30 +3665,14 @@ export class ReducerRegistry {
export const ReducerRegistryInstance: ReducerRegistry;

// @public
export class RestoreAllFrontstagesTool extends Tool {
// (undocumented)
static iconSpec: string;
// (undocumented)
run(): Promise<boolean>;
// (undocumented)
static toolId: string;
}
export const RestoreAllFrontstagesTool: typeof RestoreAllFrontstagesCoreTool & {
iconElement: React_2.ReactElement;
};

// @public
export class RestoreFrontstageLayoutTool extends Tool {
// (undocumented)
static iconSpec: string;
// (undocumented)
static get maxArgs(): number;
// (undocumented)
static get minArgs(): number;
// (undocumented)
parseAndRun(...args: string[]): Promise<boolean>;
// (undocumented)
run(frontstageId?: string): Promise<boolean>;
// (undocumented)
static toolId: string;
}
export const RestoreFrontstageLayoutTool: typeof RestoreFrontstageLayoutCoreTool & {
iconElement: React_2.ReactElement;
};

// @public
export const SafeAreaContext: React_2.Context<SafeAreaInsets | undefined>;
Expand Down
7 changes: 7 additions & 0 deletions common/api/imodel-components-react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { ScreenViewport } from '@itwin/core-frontend';
import type { Slider } from '@itwin/itwinui-react';
import type { StandardViewId } from '@itwin/core-frontend';
import type { TentativePoint } from '@itwin/core-frontend';
import type { ToolType } from '@itwin/core-frontend';
import type { TypeEditor } from '@itwin/components-react';
import { UiEvent } from '@itwin/appui-abstract';
import type { UnitProps } from '@itwin/core-quantity';
Expand Down Expand Up @@ -748,6 +749,12 @@ export enum TimelineScale {
Years = 0
}

// @public
export namespace ToolUtilities {
export function defineIcon<T extends ToolType>(toolType: T, iconElement: React_2.ReactElement): ToolWithIcon<T>;
export function isWithIcon<T extends ToolType>(toolType: T): toolType is ToolWithIcon<T>;
}

// @public
export class UiIModelComponents {
static initialize(): Promise<void>;
Expand Down
4 changes: 2 additions & 2 deletions common/api/summary/appui-react.exports.csv
Original file line number Diff line number Diff line change
Expand Up @@ -523,8 +523,8 @@ public;class;ReducerRegistry
deprecated;class;ReducerRegistry
beta;const;ReducerRegistryInstance
deprecated;const;ReducerRegistryInstance
public;class;RestoreAllFrontstagesTool
public;class;RestoreFrontstageLayoutTool
public;const;RestoreAllFrontstagesTool
public;const;RestoreFrontstageLayoutTool
public;const;SafeAreaContext
public;enum;SafeAreaInsets
public;class;ScheduleAnimationTimelineDataProvider
Expand Down
1 change: 1 addition & 0 deletions common/api/summary/imodel-components-react.exports.csv
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public;interface;TimelineMenuItemProps
public;enum;TimelinePausePlayAction
public;interface;TimelinePausePlayArgs
public;enum;TimelineScale
public;namespace;ToolUtilities
public;class;UiIModelComponents
public;class;ViewClassFullNameChangedEvent
deprecated;class;ViewClassFullNameChangedEvent
Expand Down
10 changes: 10 additions & 0 deletions common/changes/@itwin/appui-react/tool-icons_2024-12-11-13-21.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/appui-react",
"comment": "",
"type": "none"
}
],
"packageName": "@itwin/appui-react"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/imodel-components-react",
"comment": "Add `ToolUtilities` to define a Tool icon as a React element.",
"type": "none"
}
],
"packageName": "@itwin/imodel-components-react"
}
46 changes: 46 additions & 0 deletions docs/changehistory/NextVersion.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,55 @@

- [@itwin/components-react](#itwincomponents-react)
- [Additions](#additions)
- [@itwin/imodel-components-react](#itwinimodel-components-react)
- [Additions](#additions-1)

## @itwin/components-react

### Additions

- Added a callback to `VirtualizedPropertyGrid` which determines which editors should always be visible. [#1090](https://github.com/iTwin/appui/pull/1090)

## @itwin/imodel-components-react

### Additions

- Added `ToolUtilities` namespace that contains utilities for working with iTwin.js core `Tool` class. [1150](https://github.com/iTwin/appui/pull/1150)

- `ToolUtilities.defineIcon` function allows defining an icon for a tool type using a React element. This is a supplement for an existing `Tool.iconSpec` property that adds additional `iconElement` property to the tool type.

```tsx
// Before
export class MyTool extends Tool {
public static iconSpec = "icon-placeholder";
}

// After
class MyCoreTool extends Tool {
public static iconSpec = "icon-placeholder";
}
export const MyTool = ToolUtilities.defineIcon(
MyCoreTool,
<SvgPlaceholder />
);
```

Alternatively, consumers can simply add an `iconElement` property of `ReactElement` type to the tool class.

```tsx
export class MyTool extends Tool {
public static iconSpec = "icon-placeholder";
public static iconElement = (<SvgPlaceholder />);
}
```

> [!NOTE]
> Newly defined `iconElement` property needs to be read by the consumers to display the icon in a toolbar, unless the `ToolbarItemUtilities.createForTool` helper is used when creating toolbar items.

- `ToolUtilities.isWithIcon` function is a type guard that checks if a tool has a React icon element defined. Which is useful to read the icon element from the tool type.

```tsx
if (ToolUtilities.isWithIcon(MyTool)) {
MyTool.iconElement; // ReactElement
}
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions ui/appui-react/src/appui-react/icons/SvgViewLayouts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Utilities
*/

import * as React from "react";

/** @internal */
export function SvgViewLayouts() {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="m15 0h-14a1 1 0 0 0 -1 1v14a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-14a1 1 0 0 0 -1-1zm0 8h-1v2h1v5h-6v-1h-2v1h-6v-5h1v-2h-1v-5h6v1h2v-1h6zm-4 2h-2v2h-2v-2h-2v-2h2v-2h2v2h2z" />
</svg>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
ToolbarGroupItem,
} from "./ToolbarItem.js";
import { isArgsUtil } from "../backstage/BackstageItemUtilities.js";
import { ToolUtilities } from "@itwin/imodel-components-react";

/** Helper namespace to create toolbar items.
* @public
Expand Down Expand Up @@ -221,6 +222,10 @@ export namespace ToolbarItemUtilities {
toolType: ToolType,
overrides?: Partial<ToolbarActionItem>
): ToolbarActionItem {
const iconNode = ToolUtilities.isWithIcon(toolType)
? toolType.iconElement
: undefined;

// eslint-disable-next-line @typescript-eslint/no-deprecated
return ToolbarItemUtilities.createActionItem(
toolType.toolId,
Expand All @@ -232,6 +237,7 @@ export namespace ToolbarItemUtilities {
},
{
description: toolType.description,
iconNode,
...overrides,
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,15 @@
/** @packageDocumentation
* @module Tools
*/

import * as React from "react";
import { clearKeyinPaletteHistory } from "../popup/KeyinPalettePanel.js";
import { Tool } from "@itwin/core-frontend";
import svgRemove from "@bentley/icons-generic/icons/remove.svg";
import { ToolUtilities } from "@itwin/imodel-components-react";
import { SvgRemove } from "@itwin/itwinui-icons-react";

/**
* Immediate tool that will clear the recent history of command/tool keyins shown in
* the command palette.
* @alpha
*/
export class ClearKeyinPaletteHistoryTool extends Tool {
class ClearKeyinPaletteHistoryCoreTool extends Tool {
public static override toolId = "ClearKeyinPaletteHistory";
public static override iconSpec = svgRemove;
public static override iconSpec = "icon-remove";

public static override get minArgs() {
return 0;
Expand All @@ -31,3 +27,13 @@ export class ClearKeyinPaletteHistoryTool extends Tool {
return true;
}
}

/**
* Immediate tool that will clear the recent history of command/tool keyins shown in
* the command palette.
* @alpha
*/
export const ClearKeyinPaletteHistoryTool = ToolUtilities.defineIcon(
ClearKeyinPaletteHistoryCoreTool,
<SvgRemove />
);
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@
/** @packageDocumentation
* @module Tools
*/
import * as React from "react";
import { Tool } from "@itwin/core-frontend";
import { ToolUtilities } from "@itwin/imodel-components-react";
import { SvgSettings } from "@itwin/itwinui-icons-react";
import { SettingsModalFrontstage } from "../frontstage/ModalSettingsStage.js";
import svgSettings from "@bentley/icons-generic/icons/settings.svg";

/**
* Immediate tool that will open the Settings modal stage.
* @alpha
*/
export class OpenSettingsTool extends Tool {
class OpenSettingsCoreTool extends Tool {
public static override toolId = "OpenSettings";
public static override iconSpec = svgSettings;
public static override iconSpec = "icon-settings";

public static override get minArgs() {
return 0;
Expand All @@ -33,3 +31,12 @@ export class OpenSettingsTool extends Tool {
return this.run(args[0]);
}
}

/**
* Immediate tool that will open the Settings modal stage.
* @alpha
*/
export const OpenSettingsTool = ToolUtilities.defineIcon(
OpenSettingsCoreTool,
<SvgSettings />
);
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/** @packageDocumentation
* @module Tools
*/
import * as React from "react";
import {
IModelApp,
NotifyMessageDetails,
Expand All @@ -14,16 +15,12 @@ import {
import type { FrontstageDef } from "../frontstage/FrontstageDef.js";
import { InternalFrontstageManager } from "../frontstage/InternalFrontstageManager.js";
import { UiFramework } from "../UiFramework.js";
import svgViewLayouts from "@bentley/icons-generic/icons/view-layouts.svg";
import { SvgViewLayouts } from "../icons/SvgViewLayouts.js";
import { ToolUtilities } from "@itwin/imodel-components-react";

/**
* Immediate tool that will reset the layout to that specified in the stage definition. A stage Id
* may be passed in, if not the active stage is used. The stage Id is case sensitive.
* @public
*/
export class RestoreFrontstageLayoutTool extends Tool {
class RestoreFrontstageLayoutCoreTool extends Tool {
public static override toolId = "RestoreFrontstageLayout";
public static override iconSpec = svgViewLayouts;
public static override iconSpec = "icon-view-layouts";

public static override get minArgs() {
return 0;
Expand Down Expand Up @@ -57,15 +54,25 @@ export class RestoreFrontstageLayoutTool extends Tool {
public override async parseAndRun(...args: string[]): Promise<boolean> {
return this.run(args[0]);
}

public getIconNode() {
return <SvgViewLayouts />;
}
}

/**
* Immediate tool that will reset the layout of all frontstages to that specified in the stage definition.
* Immediate tool that will reset the layout to that specified in the stage definition. A stage Id
* may be passed in, if not the active stage is used. The stage Id is case sensitive.
* @public
*/
export class RestoreAllFrontstagesTool extends Tool {
export const RestoreFrontstageLayoutTool = ToolUtilities.defineIcon(
RestoreFrontstageLayoutCoreTool,
<SvgViewLayouts />
);

class RestoreAllFrontstagesCoreTool extends Tool {
public static override toolId = "RestoreAllFrontstages";
public static override iconSpec = svgViewLayouts;
public static override iconSpec = "icon-view-layouts";

public override async run() {
const frontstages = InternalFrontstageManager.frontstageDefs;
Expand All @@ -75,3 +82,12 @@ export class RestoreAllFrontstagesTool extends Tool {
return true;
}
}

/**
* Immediate tool that will reset the layout of all frontstages to that specified in the stage definition.
* @public
*/
export const RestoreAllFrontstagesTool = ToolUtilities.defineIcon(
RestoreAllFrontstagesCoreTool,
<SvgViewLayouts />
);
Loading

0 comments on commit 87ca8e3

Please sign in to comment.