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

[Tree-widget]: search warning states #1235

Merged
Merged
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion apps/test-viewer/package.json
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@
"@itwin/itwinui-icons": "5.0.0-alpha.4",
"@itwin/itwinui-illustrations-react": "^2.1.0",
"@itwin/itwinui-react": "3.17.2",
"@itwin/itwinui-react-v5": "npm:@itwin/[email protected].7",
"@itwin/itwinui-react-v5": "npm:@itwin/[email protected].8",
"@itwin/map-layers": "workspace:*",
"@itwin/map-layers-auth": "^4.10.2",
"@itwin/map-layers-formats": "^4.10.2",
28 changes: 13 additions & 15 deletions apps/test-viewer/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions packages/itwin/tree-widget/package.json
Original file line number Diff line number Diff line change
@@ -53,7 +53,7 @@
"@itwin/components-react": "^4.10.0 || ^5.0.0",
"@itwin/core-frontend": "^4.0.0",
"@itwin/ecschema-metadata": "^4.0.0",
"@itwin/itwinui-react": "5.0.0-alpha.7",
"@itwin/itwinui-react": "5.0.0-alpha.8",
"@itwin/itwinui-icons": "5.0.0-alpha.4",
"@itwin/presentation-components": "^5.0.0",
"react": "^17.0.0 || ^18.0.0",
@@ -62,7 +62,7 @@
"dependencies": {
"@itwin/presentation-core-interop": "^1.3.0",
"@itwin/presentation-hierarchies": "^1.4.1",
"@itwin/presentation-hierarchies-react": "2.0.0-alpha.6",
"@itwin/presentation-hierarchies-react": "2.0.0-alpha.7",
"@itwin/presentation-shared": "^1.2.0",
"@itwin/unified-selection": "^1.3.0",
"classnames": "^2.5.1",
@@ -90,7 +90,7 @@
"@itwin/ecschema-rpcinterface-impl": "^4.10.2",
"@itwin/eslint-plugin": "^4.1.1",
"@itwin/imodel-components-react": "^5.0.5",
"@itwin/itwinui-react": "5.0.0-alpha.7",
"@itwin/itwinui-react": "5.0.0-alpha.8",
"@itwin/itwinui-icons": "5.0.0-alpha.4",
"@itwin/oidc-signin-tool": "^4.4.0",
"@itwin/presentation-backend": "^4.10.2",
80 changes: 40 additions & 40 deletions packages/itwin/tree-widget/pnpm-lock.yaml

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions packages/itwin/tree-widget/public/locales/en/TreeWidget.json
Original file line number Diff line number Diff line change
@@ -93,9 +93,11 @@
}
},
"filtering": {
"noMatches": "There are no categories matching filter \"{{filter}}\"",
"noMatches": "No results were found for your search query.",
"noMatchesRetry": "Please modify your search and try again.",
"unknownFilterError": "An unknown error occurred while filtering the tree.",
"tooManyFilterMatches": "There are too many matches for the given filter. Please be more specific."
"tooManyFilterMatches": "There are too many matches for your search query.",
"tooManyFilterMatchesRetry": "Please refine your search and try again."
}
},
"modelsTree": {
@@ -131,10 +133,12 @@
}
},
"filtering": {
"noMatches": "There are no nodes matching filter \"{{filter}}\"",
"noMatches": "No results were found for your search query.",
"noMatchesRetry": "Please modify your search and try again.",
"unknownFilterError": "An unknown error occurred while filtering the tree.",
"unknownInstanceFocusError": "An unknown error occurred while focusing instances. <link>Disable the focus mode</link>.",
"tooManyFilterMatches": "There are too many matches for the given filter. Please be more specific.",
"tooManyFilterMatches": "There are too many matches for your search query.",
"tooManyFilterMatchesRetry": "Please refine your search and try again.",
"tooManyInstancesFocused": "There are too many elements selected for focus mode. Please select fewer elements or <link>disable the focus mode</link>."
}
},
Original file line number Diff line number Diff line change
@@ -92,14 +92,15 @@ test.describe("Categories tree", () => {
const node = locateNode(treeWidget, "Equipment", 0);
await node.focus();
const treeContainer = page.locator("#tw-tree-renderer-container");
const visibilityAction = node.getByRole("button", { name: "Visible" });
await expect(visibilityAction).toBeVisible();

await takeScreenshot(page, node, { boundingComponent: treeContainer, expandBy: { top: 10, bottom: 10 } });

// focus on checkbox using keyboard
await page.keyboard.press("Tab");

// ensure visibility action is focused
const visibilityAction = node.getByRole("button", { name: "Visible" });
await expect(visibilityAction).toBeFocused();
await takeScreenshot(page, node, { boundingComponent: treeContainer, expandBy: { top: 10, bottom: 10 } });

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions packages/itwin/tree-widget/src/e2e-tests/ModelsTree.test.ts
Original file line number Diff line number Diff line change
@@ -135,7 +135,7 @@ test.describe("Models tree", () => {
await treeWidget.getByPlaceholder("Search...").fill("Test");

// wait for no nodes to be found matching search input
await treeWidget.getByText(`There are no nodes matching filter "Test"`).waitFor();
await treeWidget.getByText(`No results were found for your search query.`).waitFor();
await takeScreenshot(page, treeWidget);
});

@@ -147,7 +147,7 @@ test.describe("Models tree", () => {
await treeWidget.getByPlaceholder("Search...").fill("x");

// wait for error message to be displayed
await treeWidget.getByText(`There are too many matches for the given filter. Please be more specific.`).waitFor();
await treeWidget.getByText(`There are too many matches for your search query.`).waitFor();
await takeScreenshot(page, treeWidget);
});

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ export function ErrorState({ resetErrorBoundary }: FallbackProps) {
return (
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", height: "100%", gap: "0.5rem" }}>
<Icon href={errorSvg} size="large" />
<Text>{TreeWidget.translate("errorState.description")}</Text>
<Text variant={"body-sm"}>{TreeWidget.translate("errorState.description")}</Text>
<Button onClick={resetErrorBoundary}>{TreeWidget.translate("errorState.retryButtonLabel")}</Button>
</div>
);
Original file line number Diff line number Diff line change
@@ -8,11 +8,9 @@ import { assert } from "@itwin/core-bentley";
import categorySvg from "@itwin/itwinui-icons/bis-category-3d.svg";
import subcategorySvg from "@itwin/itwinui-icons/bis-category-subcategory.svg";
import definitionContainerSvg from "@itwin/itwinui-icons/bis-definitions-container.svg";
import { Text } from "@itwin/itwinui-react/bricks";
import { createECSqlQueryExecutor } from "@itwin/presentation-core-interop";
import { HierarchyFilteringPath, HierarchyNodeIdentifier } from "@itwin/presentation-hierarchies";
import { TreeWidget } from "../../../TreeWidget.js";
import { EmptyTreeContent } from "../common/components/EmptyTreeContent.js";
import { EmptyTreeContent, FilterUnknownError, NoFilterMatches, TooManyFilterMatches } from "../common/components/EmptyTree.js";
import { FilterLimitExceededError } from "../common/TreeErrors.js";
import { useTelemetryContext } from "../common/UseTelemetryContext.js";
import { CategoriesTreeDefinition } from "./CategoriesTreeDefinition.js";
@@ -28,7 +26,6 @@ import type { Viewport } from "@itwin/core-frontend";
import type { PresentationHierarchyNode } from "@itwin/presentation-hierarchies-react";
import type { VisibilityTreeRendererProps } from "../common/components/VisibilityTreeRenderer.js";
import type { CategoryInfo } from "../common/CategoriesVisibilityUtils.js";

type CategoriesTreeFilteringError = "tooManyFilterMatches" | "unknownFilterError";
type HierarchyFilteringPaths = Awaited<ReturnType<Required<VisibilityTreeProps>["getFilteredPaths"]>>;

@@ -184,10 +181,13 @@ async function getCategoriesFromPaths(paths: HierarchyFilteringPaths, idsCache:

function getEmptyTreeContentComponent(filter?: string, error?: CategoriesTreeFilteringError, emptyTreeContent?: React.ReactNode) {
if (error) {
return <Text>{TreeWidget.translate(`categoriesTree.filtering.${error}`)}</Text>;
if (error === "tooManyFilterMatches") {
return <TooManyFilterMatches base={"categoriesTree"} />;
}
return <FilterUnknownError base={"categoriesTree"} />;
}
if (filter) {
return <Text>{TreeWidget.translate("categoriesTree.filtering.noMatches", { filter })}</Text>;
return <NoFilterMatches base={"categoriesTree"} />;
}
if (emptyTreeContent) {
return emptyTreeContent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

.tw-filter-empty-tree-container {
display: flex;
flex-direction: column;
gap: var(--iui-size-s);
text-align: center;
padding-right: var(--iui-size-xl);
padding-left: var(--iui-size-xl);
}

.tw-empty-tree-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: var(--iui-size-s);
gap: var(--iui-size-xs);
width: 100%;
height: 100%;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

import "./EmptyTree.css";
import { Icon, Text } from "@itwin/itwinui-react/bricks";
import { TreeWidget } from "../../../../TreeWidget.js";

interface FilterEmptyTreeProps {
base: string;
}

/** @internal */
export function TooManyFilterMatches({ base }: FilterEmptyTreeProps) {
return (
<div className={"tw-filter-empty-tree-container"}>
<Text variant={"body-sm"}>{TreeWidget.translate(`${base}.filtering.tooManyFilterMatches`)}</Text>
<Text variant={"body-sm"}>{TreeWidget.translate(`${base}.filtering.tooManyFilterMatchesRetry`)}</Text>
</div>
);
}

/** @internal */
export function NoFilterMatches({ base }: FilterEmptyTreeProps) {
return (
<div className={"tw-filter-empty-tree-container"}>
<Text variant={"body-sm"}>{TreeWidget.translate(`${base}.filtering.noMatches`)}</Text>
<Text variant={"body-sm"}>{TreeWidget.translate(`${base}.filtering.noMatchesRetry`)}</Text>
</div>
);
}

/** @internal */
export function FilterUnknownError({ base }: FilterEmptyTreeProps) {
return (
<div className={"tw-filter-empty-tree-container"}>
<Text variant={"body-sm"}>{TreeWidget.translate(`${base}.filtering.unknownFilterError`)}</Text>
</div>
);
}

interface EmptyTreeContentProps {
icon?: string;
}

/** @internal */
export function EmptyTreeContent({ icon }: EmptyTreeContentProps) {
return (
<div className={"tw-empty-tree-container"}>
{icon ? <Icon size="large" href={icon} /> : null}
<Text variant={"body-sm"} style={{ textAlign: "center" }}>
{TreeWidget.translate("baseTree.dataIsNotAvailable")}
</Text>
</div>
);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ import { useNodeHighlighting } from "../UseNodeHighlighting.js";
import { useReportingAction, useTelemetryContext } from "../UseTelemetryContext.js";
import { createIModelAccess } from "../Utils.js";
import { Delayed } from "./Delayed.js";
import { EmptyTreeContent } from "./EmptyTreeContent.js";
import { EmptyTreeContent } from "./EmptyTree.js";
import { ProgressOverlay } from "./ProgressOverlay.js";

import type { BaseTreeRendererProps } from "./BaseTreeRenderer.js";
@@ -26,7 +26,6 @@ import type { IModelConnection } from "@itwin/core-frontend";
import type { SchemaContext } from "@itwin/ecschema-metadata";
import type { PresentationHierarchyNode, SelectionStorage, useIModelTree, useSelectionHandler } from "@itwin/presentation-hierarchies-react";
import type { HighlightInfo } from "../UseNodeHighlighting.js";

/** @beta */
export type TreeProps = Pick<FunctionProps<typeof useIModelTree>, "getFilteredPaths" | "getHierarchyDefinition"> &
Partial<Pick<FunctionProps<typeof useSelectionHandler>, "selectionMode">> & {
@@ -163,11 +162,7 @@ function TreeImpl({
}

if (rootNodes.length === 0 && !isLoading) {
return (
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", flexDirection: "column", width: "100%", height: "100%" }}>
{emptyTreeContent ? emptyTreeContent : <EmptyTreeContent />}
</div>
);
return <>{emptyTreeContent ? emptyTreeContent : <EmptyTreeContent />}</>;
}

const treeRendererProps: FunctionProps<TreeProps["treeRenderer"]> = {
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ import classSvg from "@itwin/itwinui-icons/bis-class.svg";
import elementSvg from "@itwin/itwinui-icons/bis-element.svg";
import documentSvg from "@itwin/itwinui-icons/document.svg";
import ecSchemaSvg from "@itwin/itwinui-icons/selection-children.svg";
import { EmptyTreeContent } from "../common/components/EmptyTreeContent.js";
import { EmptyTreeContent } from "../common/components/EmptyTree.js";
import { Tree } from "../common/components/Tree.js";
import { TreeRenderer } from "../common/components/TreeRenderer.js";
import { ExternalSourcesTreeComponent } from "./ExternalSourcesTreeComponent.js";
@@ -16,7 +16,6 @@ import { ExternalSourcesTreeDefinition } from "./ExternalSourcesTreeDefinition.j
import type { PresentationHierarchyNode } from "@itwin/presentation-hierarchies-react";
import type { BaseTreeRendererProps } from "../common/components/BaseTreeRenderer.js";
import type { TreeProps } from "../common/components/Tree.js";

/** @beta */
export type ExternalSourcesTreeProps = Pick<TreeProps, "imodel" | "getSchemaContext" | "selectionStorage" | "selectionMode" | "emptyTreeContent"> &
Pick<BaseTreeRendererProps, "actions"> & {
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ import subjectSvg from "@itwin/itwinui-icons/bis-subject.svg";
import groupSvg from "@itwin/itwinui-icons/group.svg";
import modelSvg from "@itwin/itwinui-icons/model-cube.svg";
import hierarchyTreeSvg from "@itwin/itwinui-icons/selection-children.svg";
import { EmptyTreeContent } from "../common/components/EmptyTreeContent.js";
import { EmptyTreeContent } from "../common/components/EmptyTree.js";
import { Tree } from "../common/components/Tree.js";
import { TreeRenderer } from "../common/components/TreeRenderer.js";
import { IModelContentTreeComponent } from "./IModelContentTreeComponent.js";
@@ -20,7 +20,6 @@ import { IModelContentTreeIdsCache } from "./internal/IModelContentTreeIdsCache.
import type { PresentationHierarchyNode } from "@itwin/presentation-hierarchies-react";
import type { BaseTreeRendererProps } from "../common/components/BaseTreeRenderer.js";
import type { TreeProps } from "../common/components/Tree.js";

/** @beta */
export type IModelContentTreeProps = Pick<TreeProps, "imodel" | "getSchemaContext" | "selectionStorage" | "selectionMode" | "emptyTreeContent"> &
Pick<BaseTreeRendererProps, "actions"> & {
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ import { Anchor, Text } from "@itwin/itwinui-react/bricks";
import { createECSqlQueryExecutor } from "@itwin/presentation-core-interop";
import { HierarchyNodeIdentifier, HierarchyNodeKey } from "@itwin/presentation-hierarchies";
import { TreeWidget } from "../../../TreeWidget.js";
import { EmptyTreeContent } from "../common/components/EmptyTreeContent.js";
import { EmptyTreeContent, FilterUnknownError, NoFilterMatches, TooManyFilterMatches } from "../common/components/EmptyTree.js";
import { useFocusedInstancesContext } from "../common/FocusedInstancesContext.js";
import { FilterLimitExceededError } from "../common/TreeErrors.js";
import { useIModelChangeListener } from "../common/UseIModelChangeListener.js";
@@ -261,10 +261,13 @@ function getEmptyTreeContentComponent(filter?: string, error?: ModelsTreeFilteri
return <InstanceFocusError error={error!} />;
}
if (isFilterError(error)) {
return <Text>{TreeWidget.translate(`modelsTree.filtering.${error}`)}</Text>;
if (error === "tooManyFilterMatches") {
return <TooManyFilterMatches base={"modelsTree"} />;
}
return <FilterUnknownError base={"modelsTree"} />;
}
if (filter) {
return <Text>{TreeWidget.translate("modelsTree.filtering.noMatches", { filter })}</Text>;
return <NoFilterMatches base={"modelsTree"} />;
}
if (emptyTreeContent) {
return emptyTreeContent;
@@ -283,7 +286,7 @@ function isInstanceFocusError(error: ModelsTreeFilteringError | undefined) {
function InstanceFocusError({ error }: { error: ModelsTreeFilteringError }) {
const { toggle } = useFocusedInstancesContext();
const localizedMessage = createLocalizedMessage(TreeWidget.translate(`modelsTree.filtering.${error}`), () => toggle());
return <Text>{localizedMessage}</Text>;
return <Text variant={"body-md"}>{localizedMessage}</Text>;
}

function getIcon(node: PresentationHierarchyNode): string | undefined {