Skip to content

Commit

Permalink
Remove dependency on sprite loader (#3369)
Browse files Browse the repository at this point in the history
* WIP

* WIP

* Remove all sprite references from ui-items-providers-test

* Testing for IconComponent web component icons
Change IconComponent tests to react-teating-library
Remove unused code from IconComponent

* use import statement to load local svgs

* Remove sprite usage from teh core packages
Deprecate sprite functionality

* rush change

* Remove  most sprite reference from ui-test-app,
leaving some in for test coverage.

* Extract-api

* correct doc typo

* Edit docs for clarity.

* Fix lint errors
Update NextVersion with deprecations

* Doc correction

* Fix length in getWebComponentSource

* Fix bad docs links
  • Loading branch information
NancyMcCallB authored Mar 17, 2022
1 parent 7da1769 commit 7d7423e
Show file tree
Hide file tree
Showing 60 changed files with 361 additions and 205 deletions.
6 changes: 6 additions & 0 deletions common/api/appui-abstract.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1200,9 +1200,15 @@ export interface IconListEditorParams extends BasePropertyEditorParams {

// @public
export class IconSpecUtilities {
// @deprecated
static createSvgIconSpec(svgSrc: string): string;
static createWebComponentIconSpec(srcString: string): string;
// @deprecated
static getSvgSource(iconSpec: string): string | undefined;
static getWebComponentSource(iconSpec: string): string | undefined;
static readonly SVG_PREFIX = "svg:";
// (undocumented)
static readonly WEB_COMPONENT_PREFIX = "webSvg:";
}

// @internal
Expand Down
4 changes: 2 additions & 2 deletions common/api/core-react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2017,13 +2017,13 @@ export interface SvgPathProps extends CommonProps {
viewBoxWidth: number;
}

// @public
// @public @deprecated
export class SvgSprite extends React.PureComponent<SvgSpriteProps> {
// (undocumented)
render(): JSX.Element;
}

// @public
// @public @deprecated
export interface SvgSpriteProps extends CommonProps {
src: string;
}
Expand Down
2 changes: 2 additions & 0 deletions common/api/summary/core-react.exports.csv
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,9 @@ deprecated;Subheading2(props: TextProps): JSX.Element
public;SvgPath
public;SvgPathProps
public;SvgSprite
deprecated;SvgSprite
public;SvgSpriteProps
deprecated;SvgSpriteProps
public;TabLabel
public;Tabs
public;TabsProps
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/appui-abstract",
"comment": "Implement svg icons loading as a web component.",
"type": "none"
}
],
"packageName": "@itwin/appui-abstract"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/appui-layout-react",
"comment": "Implement svg icons loading as a web component.",
"type": "none"
}
],
"packageName": "@itwin/appui-layout-react"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/appui-react",
"comment": "Implement svg icons loading as a web component.",
"type": "none"
}
],
"packageName": "@itwin/appui-react"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/components-react",
"comment": "Implement svg icons loading as a web component.",
"type": "none"
}
],
"packageName": "@itwin/components-react"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/core-react",
"comment": "Implement svg icons loading as a web component.",
"type": "none"
}
],
"packageName": "@itwin/core-react"
}
4 changes: 4 additions & 0 deletions docs/changehistory/NextVersion.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ iTwin.js applications can now check [WebGLRenderCompatibilityInfo.usingIntegrate
## ColorByName is an object, not an enum

Enums in TypeScript have some shortcomings, one of which resulted in a bug that caused [ColorDef.fromString]($common) to return [ColorDef.black]($common) for some valid color strings like "aqua". This is due to several standard color names ("aqua" and "cyan", "magenta" and "fuschia", and several "grey" vs "gray" variations) having the same numeric values. To address this, [ColorByName]($common) has been converted from an `enum` to a `namespace`. Code that accesses `ColorByName` members by name will continue to compile with no change.

## Deprecations in @itwin/core-react package

Using the sprite loader for SVG icons is deprecated. This includes [SvgSprite]($core-react) and the methods getSvgIconSpec() and getSvgIconSource() methods on [IconSpecUtilities]($appui-abstract). The sprite loader has been replaced with a web component [IconWebComponent]($core-react) used by [Icon]($core-react) to load SVGs onto icons.
6 changes: 3 additions & 3 deletions docs/learning/ui/appui-react/Backstage.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ These overlays are an implementation of a modal frontstage. The backstage is ope
To ensure that an extension can supply items for the Backstage menu, it should be created using the [BackstageComposer]($appui-react) component. The example below shows how to provide [BackstageActionItem]($appui-abstract) and [BackstageStageLauncher]($appui-abstract) item to the BackstageComposer.

```tsx
import stageIconSvg from "@bentley/icons-generic/icons/imodeljs.svg?sprite";
import settingsIconSvg from "@bentley/icons-generic/icons/settings.svg?sprite";
import stageIconSvg from "@bentley/icons-generic/icons/imodeljs.svg";
import settingsIconSvg from "@bentley/icons-generic/icons/settings.svg";

export function AppBackstageComposer() {
const [backstageItems] = React.useState(() => [
BackstageItemUtilities.createStageLauncher("app.SampleFrontstage", 100, 10, IModelApp.i18n.translate("app:backstage.sampleFrontstage"), undefined, IconSpecUtilities.createSvgIconSpec(stageIconSvg)),
BackstageItemUtilities.createStageLauncher("app.SampleFrontstage", 100, 10, IModelApp.i18n.translate("app:backstage.sampleFrontstage"), undefined, IconSpecUtilities.createWebComponentIconSpec(stageIconSvg)),
SettingsModalFrontstage.getBackstageActionItem (300, 10),
]);

Expand Down
44 changes: 20 additions & 24 deletions docs/learning/ui/core/Icon.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@

The [Icon]($core-react:Icon) category in the `@itwin/core-react` package includes components that render icons when given an icon name or SVG source or path.

The [Icon]($core-react) React component displays an icon based on an IconSpec.
An [IconSpec]($core-react:Icon) can be a string, ReactNode or
[ConditionalStringValue]($appui-abstract).
When the IconSpec is a string, the value is either a Webfont name or a formatted string that includes an imported SVG.
When using an SVG, it must be imported using the webpack loader `svg-sprite-loader`.
The formatted string begins with "svg:".
The `IconSpecUtilities.createSvgIconSpec` can be used to format the SVG string. See example usage below.
The [Icon]($core-react) React component displays an icon based on an IconSpec. An [IconSpec]($core-react:Icon) can be a string, ReactNode or [ConditionalStringValue]($appui-abstract).
When the IconSpec is a string, the value is either a Webfont symbol name or a formatted string that specifies an imported SVG.
When using an SVG, we use the web component svg-loader to load the SVG from its path. This web component is defined in UiCore.initialize() as follows:

The [SvgSprite]($core-react) React component displays an icon using `<svg>` and `<use>` elements to reference an imported SVG file. The SVG file must be imported using the webpack loader `svg-sprite-loader`.
```tsx
if (window.customElements.get("svg-loader") === undefined)
window.customElements.define("svg-loader", IconWebComponent);
```

If your app does not initialize UiFramework or UiCore, you'll need to define this custom element to use the svg-loader.

The [SvgSprite]($core-react) React component displays an icon using an `<svg>` element and an array of SVG paths.
The [Icon]($core-react) will automatically use the svg-loader in IconWebComponent, assuming the customElement has been defined.

The formatted svg icon string begins with "webSvg:".
The `IconSpecUtilities.createWebComponentIconSpec` can be used to format the SVG string. See example usage below.

The sprite SVG loader has been deprecated.
The [SvgSprite]($core-react) React component has been deprecated.

## Examples

Expand All @@ -32,9 +39,9 @@ This example shows how to use an SVG from `@bentley/icons-generic`.
import { IconSpecUtilities } from "@itwin/appui-abstract";
import { Icon } from "@itwin/core-react";

import placeholderSvg from "@bentley/icons-generic/icons/placeholder.svg?sprite";
import placeholderSvg from "@bentley/icons-generic/icons/placeholder.svg";
. . .
const iconSpec = IconSpecUtilities.createSvgIconSpec(placeholderSvg);
const iconSpec = IconSpecUtilities.createWebComponentIconSpec(placeholderSvg);
. . .
<Icon iconSpec={iconSpec} />
```
Expand All @@ -44,24 +51,13 @@ const iconSpec = IconSpecUtilities.createSvgIconSpec(placeholderSvg);
This example shows how to use an SVG from within the application.

```tsx
import rotateSvg from "../icons/rotate.svg?sprite";
import rotateSvg from "../icons/rotate.svg";
. . .
const iconSpec = IconSpecUtilities.createSvgIconSpec(rotateSvg);
const iconSpec = IconSpecUtilities.createWebComponentIconSpec(rotateSvg);
. . .
<Icon iconSpec={iconSpec} />
```

### SvgSprite and SVG File

Rather then using the Icon component with SVG files, the SvgSprite component can be used.

```tsx
import { Icon } from "@itwin/core-react";
import rotateSvg from "../icons/rotate.svg?sprite";
. . .
<SvgSprite src={rotateSvg} />
```

### SvgPath

When you have SVG path statements instead of SVG files, the SvgPath component can be used.
Expand Down
7 changes: 3 additions & 4 deletions test-apps/ui-items-providers-test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
"main": "lib/ui-items-providers-test.js",
"typings": "lib/ui-items-providers-test",
"scripts": {
"prebuild": "npm run -s pseudolocalize && npm run -s copy:assets && npm run -s copy:locale",
"prebuild": "npm run -s pseudolocalize && npm run -s copy:assets",
"build": "npm run -s prebuild && tsc 1>&2",
"build:ci": "npm run -s build",
"clean": "rimraf lib .rush/temp/package-deps*.json",
"copy:assets": "cpx \"./src/**/*.{*css,json,svg}\" \"./lib\" && cpx \"./src/public/**/*\" ./lib/public/",
"copy:locale": "cpx \"./src/public/locales/**/*\" ./lib/public",
"copy:assets": "cpx \"./src/**/*.{*css,json,svg}\" \"./lib\"",
"cover": "",
"docs": "",
"lint": "eslint -f visualstudio \"./src/**/*.{ts,tsx}\" 1>&2",
Expand Down Expand Up @@ -68,4 +67,4 @@
],
"extends": "plugin:@itwin/itwinjs-recommended"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ import {
} from "@itwin/core-frontend";
import { Point3d } from "@itwin/core-geometry";
import { UiFramework } from "@itwin/appui-react";
import { ToolbarItemUtilities } from "@itwin/appui-abstract";
import genericToolSvg from "./generic-tool.svg?sprite";
import { IconSpecUtilities, ToolbarItemUtilities } from "@itwin/appui-abstract";
import { UiItemsProvidersTest } from "../ui-items-providers-test";
import genericToolSvg from "./generic-tool.svg";

/** Sample Primitive tool where user selects an element for processing */
export class GenericLocateTool extends PrimitiveTool {
public userPoint: Point3d | undefined;
public elementId: string | undefined;
public static override get toolId() { return "uiItemsProvidersTest-GenericLocateTool"; }
public static get toolStringKey() { return `tools.${GenericLocateTool.toolId}.`; }
public static override iconSpec = `svg:${genericToolSvg}`;
public static override iconSpec = genericToolSvg;
public static useDefaultPosition = false;
public override autoLockTarget(): void { } // NOTE: For selecting elements we only care about iModel, so don't lock target model automatically.
protected wantSelectionClearOnMiss(_ev: BeButtonEvent): boolean { return SelectionMode.Replace === this.getSelectionMode(); }
Expand Down Expand Up @@ -120,7 +120,8 @@ export class GenericLocateTool extends PrimitiveTool {

public static getActionButtonDef(itemPriority: number, groupPriority?: number) {
const overrides = undefined !== groupPriority ? { groupPriority } : {};
return ToolbarItemUtilities.createActionButton(GenericLocateTool.toolId, itemPriority, GenericLocateTool.iconSpec, GenericLocateTool.flyover,
const iconSpec = IconSpecUtilities.createWebComponentIconSpec(this.iconSpec);
return ToolbarItemUtilities.createActionButton(GenericLocateTool.toolId, itemPriority, iconSpec, GenericLocateTool.flyover,
async () => { await IModelApp.tools.run(GenericLocateTool.toolId, IModelApp.viewManager.selectedView, true); },
overrides);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ import { ModalDialogManager } from "@itwin/appui-react";
import { SampleModalDialog } from "../ui/dialogs/SampleModalDialog";
import { IconSpecUtilities, ToolbarItemUtilities } from "@itwin/appui-abstract";
import { UiItemsProvidersTest } from "../ui-items-providers-test";

/** the following will import svgs into DOM and generate SymbolId that is used to locate the svg image. This
* processing is done via the 'magic' webpack plugin and requires the use or the Bentley build scripts. */
import connectedIcon from "../ui/icons/connected-query.svg?sprite";
import connectedQuerySvg from "../ui/icons/connected-query.svg";

/**
* Immediate tool that will open an example modal dialog.The tool is created and register to allow the user
Expand All @@ -24,7 +21,7 @@ import connectedIcon from "../ui/icons/connected-query.svg?sprite";
*/
export class OpenTraceDialogTool extends Tool {
public static override toolId = "uiItemsProvidersTest-OpenTraceDialogTool";
public static override iconSpec = IconSpecUtilities.createSvgIconSpec(connectedIcon);
public static override iconSpec = connectedQuerySvg;

// istanbul ignore next
public static override get minArgs() { return 0; }
Expand Down Expand Up @@ -53,7 +50,8 @@ export class OpenTraceDialogTool extends Tool {
const overrides = {
groupPriority,
};
return ToolbarItemUtilities.createActionButton(OpenTraceDialogTool.toolId, itemPriority, OpenTraceDialogTool.iconSpec, OpenTraceDialogTool.flyover,
const iconSpec = IconSpecUtilities.createWebComponentIconSpec(`${this.iconSpec}`);
return ToolbarItemUtilities.createActionButton(OpenTraceDialogTool.toolId, itemPriority, iconSpec, OpenTraceDialogTool.flyover,
async () => { await IModelApp.tools.run(OpenTraceDialogTool.toolId); },
overrides);
}
Expand Down
9 changes: 6 additions & 3 deletions test-apps/ui-items-providers-test/src/tools/SampleTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "@itwin/core-frontend";
import {
ColorEditorParams, DialogItem, DialogItemValue, DialogPropertySyncItem,
IconSpecUtilities,
InputEditorSizeParams,
PropertyDescription, PropertyEditorParamTypes, StandardEditorNames, SuppressLabelEditorParams, ToolbarItemUtilities,
} from "@itwin/appui-abstract";
Expand All @@ -22,8 +23,8 @@ import { Point3d } from "@itwin/core-geometry";
import { ColorByName, ColorDef } from "@itwin/core-common";
import { FormatterSpec } from "@itwin/core-quantity";
import { CursorInformation, MenuItemProps, UiFramework } from "@itwin/appui-react";
import sampleToolSvg from "./SampleTool.svg?sprite";
import { UiItemsProvidersTest } from "../ui-items-providers-test";
import sampleToolSvg from "./SampleTool.svg";

enum ToolOptions {
Red,
Expand All @@ -37,7 +38,7 @@ enum ToolOptions {
export class SampleTool extends PrimitiveTool {
// ensure toolId is unique by adding "uiItemsProvidersTest-" prefix
public static override toolId = "uiItemsProvidersTest-SampleTool";
public static override iconSpec = `svg:${sampleToolSvg}`;
public static override iconSpec = sampleToolSvg;
public readonly points: Point3d[] = [];
private _showCoordinatesOnPointerMove = false;
private _stationFormatterSpec?: FormatterSpec;
Expand Down Expand Up @@ -529,7 +530,9 @@ export class SampleTool extends PrimitiveTool {

public static getActionButtonDef(itemPriority: number, groupPriority?: number) {
const overrides = undefined !== groupPriority ? { groupPriority } : {};
return ToolbarItemUtilities.createActionButton(SampleTool.toolId, itemPriority, SampleTool.iconSpec, SampleTool.flyover,
const iconSpec = IconSpecUtilities.createWebComponentIconSpec(this.iconSpec);

return ToolbarItemUtilities.createActionButton(SampleTool.toolId, itemPriority, iconSpec, SampleTool.flyover,
async () => { await IModelApp.tools.run(SampleTool.toolId); },
overrides);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { IModelApp, NotifyMessageDetails, OutputMessagePriority, OutputMessageType } from "@itwin/core-frontend";
import { UiItemsProvidersTest } from "../../ui-items-providers-test";
import { CustomFrontstage } from "../frontstages/CustomContent";
import visibilityIcon from "../icons/visibility-semi-transparent.svg?sprite";
import visibilitySemiTransparentSvg from "../icons/visibility-semi-transparent.svg";

/**
* Test UiItemsProvider that provide buttons, widgets, and backstage item to NetworkTracing stage.
Expand Down Expand Up @@ -38,11 +38,12 @@ export class CustomContentUiProvider implements UiItemsProvider {
toolbarUsage === ToolbarUsage.ContentManipulation &&
toolbarOrientation === ToolbarOrientation.Horizontal
) {
const iconData = IconSpecUtilities.createWebComponentIconSpec(visibilitySemiTransparentSvg);

const getSvgTestButton = ToolbarItemUtilities.createActionButton(
"custom-visibility-tool",
-1,
IconSpecUtilities.createSvgIconSpec(visibilityIcon),
iconData,
"Custom Visibility Tool",
(): void => {
IModelApp.notifications.outputMessage(new NotifyMessageDetails(OutputMessagePriority.Info, "custom-visibility-tool activated", undefined, OutputMessageType.Toast));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

import {
AbstractStatusBarItemUtilities,
CommonStatusBarItem, CommonToolbarItem, StageUsage, StatusBarSection, ToolbarOrientation, ToolbarUsage, UiItemsProvider,
CommonStatusBarItem, CommonToolbarItem, IconSpecUtilities, StageUsage, StatusBarSection, ToolbarOrientation, ToolbarUsage, UiItemsProvider,
} from "@itwin/appui-abstract";
import { SampleTool } from "../../tools/SampleTool";
import statusBarButtonSvg from "../icons/StatusField.svg?sprite";
import { UnitsPopupUiDataProvider } from "../dialogs/UnitsPopup";
import { IModelApp } from "@itwin/core-frontend";
import { UiItemsProvidersTest } from "../../ui-items-providers-test";
import { OpenAbstractDialogTool } from "../../tools/OpenAbstractModalDialogTool";
import statusFieldSvg from "../icons/StatusField.svg";

/**
* The GeneralUiItemsProvider provides additional items to any frontstage that has a usage value of StageUsage.General.
Expand All @@ -35,7 +35,7 @@ export class GeneralUiItemsProvider implements UiItemsProvider {
}

public provideStatusBarItems(_stageId: string, stageUsage: string): CommonStatusBarItem[] {
const unitsIcon = `svg:${statusBarButtonSvg}`;
const unitsIcon = IconSpecUtilities.createWebComponentIconSpec(statusFieldSvg);
const statusBarItems: CommonStatusBarItem[] = [];
if (stageUsage === StageUsage.General) {
statusBarItems.push(
Expand Down
Loading

0 comments on commit 7d7423e

Please sign in to comment.