Skip to content

Commit

Permalink
[tree-widget]: repository tree investigation (#1104)
Browse files Browse the repository at this point in the history
* initial repository tree

* Update packages/itwin/tree-widget/src/components/trees/repositories-tree/UseRepositoriesHierarchyProvider.ts

Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com>

* Update packages/itwin/tree-widget/src/components/trees/repositories-tree/UseRepositoriesHierarchyProvider.ts

Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com>

* Update packages/itwin/tree-widget/src/components/trees/repositories-tree/FormatLabel.tsx

Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com>

* adjust provider for hierarchy-react 1.2

* Update apps/test-viewer/src/UiProvidersConfig.tsx

Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com>

* fixes

* access token adjustments

* Update apps/test-viewer/src/components/repositories-tree/RepositoriesService.ts

Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com>

* partial fixes

* aditional changes

* partial fixes

* adjustments

* adjustment

* url prop change

* merge fix

* lint fixes

* update e2e tests

* fix

---------

Co-authored-by: Grigas <35135765+grigasp@users.noreply.github.com>
  • Loading branch information
MartynasStrazdas and grigasp authored Dec 16, 2024
1 parent 11af90c commit bff18e5
Show file tree
Hide file tree
Showing 13 changed files with 434 additions and 0 deletions.
3 changes: 3 additions & 0 deletions apps/test-viewer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
"@itwin/unified-selection": "^1.1.2",
"@itwin/web-viewer-react": "^4.2.5",
"@itwin/webgl-compatibility": "^4.9.7",
"@itwin/presentation-hierarchies": "^1.3.0",
"@itwin/presentation-hierarchies-react": "^1.2.0",
"@itwin/presentation-shared": "^1.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.10",
Expand Down
9 changes: 9 additions & 0 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: 6 additions & 0 deletions apps/test-viewer/src/UiProvidersConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
TreeWidget,
TreeWidgetComponent,
} from "@itwin/tree-widget-react";
import { RepositoriesTreeComponent } from "./components/repositories-tree/RepositoriesTree";
import { useViewerOptionsContext } from "./components/ViewerOptions";
import { unifiedSelectionStorage } from "./SelectionStorage";

Expand Down Expand Up @@ -164,6 +165,11 @@ const configuredUiItems = new Map<string, UiItem>([
/>
),
},
{
id: "RepositoriesTree",
getLabel: () => "Repositories tree",
render: () => <RepositoriesTreeComponent baseUrl={`https://${globalThis.IMJS_URL_PREFIX ?? ""}api.bentley.com`} />,
},
];
return [
{
Expand Down
36 changes: 36 additions & 0 deletions apps/test-viewer/src/components/repositories-tree/FormatLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

import type { ITwinRepositoryType } from "./RepositoriesType";

/**
* @internal
*/
export function formatLabel(iTwinRepositoryType: ITwinRepositoryType) {
switch (iTwinRepositoryType) {
case "iModels":
return "iModels";
case "RealityData":
return "Reality data";
case "Storage":
return "Storage";
case "Forms":
return "Forms";
case "Issues":
return "Issues";
case "CesiumCuratedContent":
return "Cesium content";
case "SensorData":
return "Sensor data";
case "GeographicInformationSystem":
return "Geographic information system";
case "Construction":
return "Construction";
case "Subsurface":
return "Subsurface";
default:
return "Unknown";
}
}
94 changes: 94 additions & 0 deletions apps/test-viewer/src/components/repositories-tree/GetIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

import {
Svg3D,
SvgClash,
SvgClipboard,
SvgDetails,
SvgDocument,
SvgDocumentation,
SvgFind,
SvgFlag,
SvgFolder,
SvgGlobe,
SvgImodelHollow,
SvgInfo,
SvgIssueReport,
SvgItem,
SvgList,
SvgRealityMesh,
} from "@itwin/itwinui-icons-react";
import type { ITwinRepositoryType } from "./RepositoriesType";

import type { PresentationHierarchyNode } from "@itwin/presentation-hierarchies-react";
const StorageNodeIcons: { [key: string]: React.JSX.Element } = {
folder: <SvgFolder />,
file: <SvgDocument />,
};

const IssuesNodeIcons: { [key: string]: React.JSX.Element } = {
Issue: <SvgFlag />,
Clash: <SvgClash />,
Observation: <SvgFind />,
RFI: <SvgInfo />,
"Daily Log": <SvgClipboard />,
Punchlist: <SvgList />,
};

const RealityDataNodeIcons: { [key: string]: React.JSX.Element } = {
RealityMesh3DTiles: <SvgRealityMesh />,
OMR: <SvgItem />,
};

const FormsNodeIcons: { [key: string]: React.JSX.Element } = {
"Asset Inspection": <SvgClipboard />,
Other: <SvgDocumentation />,
};

/**
* @internal
*/
export function getRepositoryNodeIcon(node: PresentationHierarchyNode) {
if (node.nodeData.parentKeys.length === 0) {
return getRootNodeIcon(node.nodeData.extendedData?.repositoryType);
}

switch (node.nodeData.extendedData?.repositoryType) {
case "Storage":
return StorageNodeIcons[node.nodeData.extendedData.type];
case "Issues":
return IssuesNodeIcons[node.nodeData.extendedData.type];
case "Forms":
return FormsNodeIcons[node.nodeData.extendedData.type];
case "RealityData":
return RealityDataNodeIcons[node.nodeData.extendedData.type];
default:
return <SvgItem />;
}
}

function getRootNodeIcon(repositoryType: ITwinRepositoryType) {
switch (repositoryType) {
case "iModels":
return <SvgImodelHollow />;
case "RealityData":
return <Svg3D />;
case "Storage":
return <SvgFolder />;
case "Forms":
return <SvgDetails />;
case "Issues":
return <SvgIssueReport />;
case "CesiumCuratedContent":
return <SvgGlobe />;
case "SensorData":
case "GeographicInformationSystem":
case "Construction":
case "Subsurface":
default:
return <SvgItem />;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

import { IModelApp } from "@itwin/core-frontend";
import type { ITwinRepositoryType } from "./RepositoriesType";

interface RepositoryData {
id?: string;
displayName: string;
type?: string;
name?: string;
}

interface ITwinRepository {
class: ITwinRepositoryType;
subClass?: string;
uri: string;
}

/**
* @internal
*/
export async function getItwinRepositories(itwinId: string, baseUrl?: string): Promise<ITwinRepository[]> {
const url = getRepositoriesUrl(itwinId, baseUrl);
const result = (await fetchData(url)) as ITwinRepository[];
return result;
}

/**
* @internal
*/
export async function getRepositoryData(url: string): Promise<RepositoryData[]> {
const result = (await fetchData(url)) as RepositoryData[];
return result;
}

async function fetchData(url: string): Promise<unknown> {
const accessToken = await IModelApp.getAccessToken();
const headers = {
Authorization: accessToken,
"Content-Type": "application/json",
Prefer: "return=representation",
};

try {
const response = await fetch(url, {
headers,
});
const result = await response.json();

// get the data avoiding links
delete result._links;
const values = Object.values(result);
const data = values[0];

return data;
} catch (error) {
throw error;
}
}

function getRepositoriesUrl(itwinId: string, baseUrl?: string) {
if (!baseUrl) {
return `https://api.bentley.com/itwins/${itwinId}/repositories`;
}

return baseUrl?.includes("https://") ? `${baseUrl}/itwins/${itwinId}/repositories` : `https://${baseUrl}/itwins/${itwinId}/repositories`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

import { useActiveIModelConnection } from "@itwin/appui-react";
import { Flex, ProgressRadial, Text } from "@itwin/itwinui-react";
import { TreeRenderer, useTree } from "@itwin/presentation-hierarchies-react";
import { TreeWidget } from "@itwin/tree-widget-react";
import { getRepositoryNodeIcon } from "./GetIcon";
import { useRepositoriesHierarchyProvider } from "./UseRepositoriesHierarchyProvider";
import { Delayed, ProgressOverlay } from "./Utils";

interface RepositoriesTreeProps {
itwinId: string;
baseUrl?: string;
noDataMessage?: string;
}

/**
* @alpha
*/
export function RepositoriesTreeComponent({ baseUrl, noDataMessage }: Omit<RepositoriesTreeProps, "itwinId">) {
const iModelConnection = useActiveIModelConnection();
const iTwinId = iModelConnection?.iTwinId;

if (!iTwinId) {
return <> No itwin id found</>;
}
return <RepositoriesTree itwinId={iTwinId} baseUrl={baseUrl} noDataMessage={noDataMessage} />;
}

function RepositoriesTree({ itwinId, noDataMessage, baseUrl }: RepositoriesTreeProps) {
const getHierarchyProvider = useRepositoriesHierarchyProvider({ itwinId, baseUrl });
const { rootNodes, isLoading, ...treeProps } = useTree({
getHierarchyProvider,
});

const treeRenderer = () => {
if (rootNodes === undefined) {
return (
<Flex alignItems="center" justifyContent="center" flexDirection="column" style={{ width: "100%", height: "100%" }}>
<Delayed show={true}>
<ProgressRadial size="large" />
</Delayed>
</Flex>
);
}

if (rootNodes.length === 0 && !isLoading) {
return (
<Flex alignItems="center" justifyContent="center" flexDirection="column" style={{ width: "100%", height: "100%" }}>
{noDataMessage ? noDataMessage : <Text>{TreeWidget.translate("baseTree.dataIsNotAvailable")}</Text>}
</Flex>
);
}

return (
<Flex.Item alignSelf="flex-start" style={{ width: "100%", overflow: "auto" }}>
<TreeRenderer rootNodes={rootNodes} {...treeProps} selectionMode={"extended"} getIcon={getRepositoryNodeIcon} />
</Flex.Item>
);
};

return (
<div style={{ position: "relative", height: "100%", overflow: "hidden" }}>
<div id="tw-tree-renderer-container" style={{ overflow: "auto", height: "100%" }}>
{treeRenderer()}
</div>
<Delayed show={isLoading}>
<ProgressOverlay />
</Delayed>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

export type ITwinRepositoryType =
| "iModels"
| "RealityData"
| "Storage"
| "Forms"
| "Issues"
| "SensorData"
| "CesiumCuratedContent"
| "GeographicInformationSystem"
| "Construction"
| "Subsurface";
Loading

0 comments on commit bff18e5

Please sign in to comment.