Skip to content

Commit

Permalink
Integrate stateless hierarchies builder into test app (#359)
Browse files Browse the repository at this point in the history
* Add tabs to switch between stateless and rules-driven tree widget

* Add tabs to switch between stateless and rules-driven tree widget

* Add changeset

* Update apps/test-app/frontend/public/locales/en/Sample.json

Co-authored-by: Grigas <[email protected]>

* Use useRef where possible

* Update id setter to not get duplicate id's

---------

Co-authored-by: Grigas <[email protected]>
  • Loading branch information
JonasDov and grigasp authored Dec 11, 2023
1 parent 76a926b commit d140061
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 56 deletions.
2 changes: 2 additions & 0 deletions .changeset/curly-students-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
3 changes: 2 additions & 1 deletion apps/test-app/frontend/public/locales/en/Sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
}
}
},
"tree": "Tree Widget",
"rules-driven-tree": "Rules-driven tree",
"stateless-tree": "Stateless tree",
"find-similar": {
"results": "Similar Instances",
"dismiss-button": {
Expand Down
2 changes: 1 addition & 1 deletion apps/test-app/frontend/src/components/app/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

.app {
display: grid;
grid-template-rows: 30px 40px calc(100vh - 30px - 40px);
grid-template-rows: 30px auto calc(100vh - 30px);
height: 100vh;
position: relative;
overflow: hidden;
Expand Down
1 change: 0 additions & 1 deletion apps/test-app/frontend/src/components/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,6 @@ function IModelComponents(props: IModelComponentsProps) {
}}
>
<TreeWidget imodel={imodel} rulesetId={rulesetId} />
{/* <ExperimentalModelsTree imodel={imodel} /> */}
<div className="app-content-right-separator">
<hr />
<ElementSeparator orientation={Orientation.Vertical} ratio={panelRatio} movableArea={panelHeight} onRatioChanged={onPanelResize} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ export function DiagnosticsSelector(props: DiagnosticsSelectorProps) {

return (
<DropdownMenu menuItems={menuItems} onClickOutside={() => {}} onHide={() => onDiagnosticsOptionsChanged(result)}>
<Button size="small">Diagnostics</Button>
<Button size="small" className="diagnostics-button">
Diagnostics
</Button>
</DropdownMenu>
);
}
24 changes: 11 additions & 13 deletions apps/test-app/frontend/src/components/tree-widget/TreeWidget.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,26 @@
.treewidget {
display: flex;
flex-direction: column;
min-width: 0;
position: relative;
}

.tree-widget-tabs-content {
padding: unset;
}

.treewidget-header {
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.25);
padding: 5px 5px 1px 5px;
padding: 0.5em;
margin-bottom: 5px;
display: grid;
grid-template-columns: auto 100px;
grid-template-rows: 24px 36px;
display: flex;
gap: 0.5em;
}

.treewidget-header h3 {
.treewidget-header .components-filtering-input {
flex-grow: 4;
margin: 0;
}

.treewidget-header .components-filtering-input {
grid-column-start: 1;
grid-column-end: span 2;
margin-left: 0;
margin-right: 0;
.treewidget-header .diagnostics-button {
flex-grow: 1;
}

.treewidget .filteredTree {
Expand Down
111 changes: 72 additions & 39 deletions apps/test-app/frontend/src/components/tree-widget/TreeWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

import { useCallback, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useResizeDetector } from "react-resize-detector";
import { PropertyRecord } from "@itwin/appui-abstract";
import {
Expand All @@ -22,6 +22,7 @@ import {
import { IModelApp, IModelConnection } from "@itwin/core-frontend";
import { SchemaContext } from "@itwin/ecschema-metadata";
import { ECSchemaRpcLocater } from "@itwin/ecschema-rpcinterface-common";
import { Tab, Tabs } from "@itwin/itwinui-react";
import { DiagnosticsProps } from "@itwin/presentation-components";
import { createECSqlQueryExecutor, createMetadataProvider } from "@itwin/presentation-core-interop";
import { HierarchyNode, HierarchyProvider } from "@itwin/presentation-hierarchy-builder";
Expand All @@ -32,16 +33,52 @@ import { Tree } from "./Tree";
interface Props {
imodel: IModelConnection;
rulesetId?: string;
height?: number;
width?: number;
}

export function TreeWidget(props: Props) {
export function TreeWidget(props: Omit<Props, "height" | "width">) {
const [openTab, setOpenTab] = useState(0);
const { width, height, ref } = useResizeDetector<HTMLDivElement>();
const tabsClassName = "tree-widget-tabs";
const [heightOfTreeWidget, setHeightOfTreeWidget] = useState(0);
useEffect(() => {
const tabElements = ref.current?.getElementsByClassName(tabsClassName);
const heightOfTab = tabElements && tabElements.length > 0 ? tabElements[0].clientHeight : 0;
setHeightOfTreeWidget(height ? height - heightOfTab : 0);
// When width changes tab height might change, so it needs to be included in dependency list
}, [height, ref, width]);

return (
<div ref={ref}>
<Tabs
labels={[
<Tab key={1} label={IModelApp.localization.getLocalizedString("Sample:controls.rules-driven-tree")} />,
<Tab key={2} label={IModelApp.localization.getLocalizedString("Sample:controls.stateless-tree")} />,
]}
onTabSelected={setOpenTab}
contentClassName="tree-widget-tabs-content"
tabsClassName={tabsClassName}
>
<div className="treewidget">
{openTab === 0 ? (
<RulesDrivenTreeWidget imodel={props.imodel} rulesetId={props.rulesetId} height={heightOfTreeWidget} width={width} />
) : (
<StatelessTreeWidget imodel={props.imodel} height={heightOfTreeWidget} width={width} />
)}
</div>
</Tabs>
</div>
);
}

export function RulesDrivenTreeWidget(props: Props) {
const { rulesetId, imodel } = props;
const [diagnosticsOptions, setDiagnosticsOptions] = useState<DiagnosticsProps>({ ruleDiagnostics: undefined, devDiagnostics: undefined });
const [filter, setFilter] = useState("");
const [filteringStatus, setFilteringStatus] = useState(FilteringInputStatus.ReadyToFilter);
const [matchesCount, setMatchesCount] = useState<number>();
const [activeMatchIndex, setActiveMatchIndex] = useState(0);

const onFilteringStateChange = useCallback((isFiltering: boolean, newMatchesCount: number | undefined) => {
setFilteringStatus(
isFiltering
Expand All @@ -52,14 +89,16 @@ export function TreeWidget(props: Props) {
);
setMatchesCount(newMatchesCount);
}, []);

const { width, height, ref } = useResizeDetector();

const [heightToUse, setHeightToUse] = useState(0);
const treeWidgetHeaderRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const heightOfHeader = treeWidgetHeaderRef.current?.clientHeight ?? 0;
const heightToSet = props.height ? props.height - heightOfHeader : 0;
setHeightToUse(heightToSet ?? 0);
}, [props.height]);
return (
<div className="treewidget">
<div className="treewidget-header">
<h3>{IModelApp.localization.getLocalizedString("Sample:controls.tree")}</h3>
<DiagnosticsSelector onDiagnosticsOptionsChanged={setDiagnosticsOptions} />
<>
<div ref={treeWidgetHeaderRef} className="treewidget-header">
{rulesetId ? (
<FilteringInput
status={filteringStatus}
Expand All @@ -78,36 +117,36 @@ export function TreeWidget(props: Props) {
}}
/>
) : null}
<DiagnosticsSelector onDiagnosticsOptionsChanged={setDiagnosticsOptions} />
</div>
<div ref={ref} className="filteredTree">
{rulesetId && width && height ? (
<div className="filteredTree">
{rulesetId && props.width && heightToUse ? (
<>
<Tree
imodel={imodel}
rulesetId={rulesetId}
diagnostics={diagnosticsOptions}
filtering={{ filter, activeMatchIndex, onFilteringStateChange }}
width={width}
height={height}
width={props.width}
height={heightToUse}
/>
{filteringStatus === FilteringInputStatus.FilteringInProgress ? <div className="filteredTreeOverlay" /> : null}
</>
) : null}
</div>
</div>
</>
);
}

export function ExperimentalModelsTree({ imodel }: { imodel: IModelConnection }) {
const { width, height, ref } = useResizeDetector();
export function StatelessTreeWidget(props: Omit<Props, "rulesetId">) {
const dataProvider = useMemo((): TreeDataProvider => {
const schemas = new SchemaContext();
schemas.addLocater(new ECSchemaRpcLocater(imodel.getRpcProps()));
schemas.addLocater(new ECSchemaRpcLocater(props.imodel.getRpcProps()));
const metadataProvider = createMetadataProvider(schemas);
const modelsTreeHierarchyProvider = new HierarchyProvider({
metadataProvider,
hierarchyDefinition: new ModelsTreeDefinition({ metadataProvider }),
queryExecutor: createECSqlQueryExecutor(imodel),
queryExecutor: createECSqlQueryExecutor(props.imodel),
});
return async (node?: TreeNodeItem): Promise<TreeNodeItem[]> => {
const parent: HierarchyNode | undefined = node ? (node as any).__internal : undefined;
Expand All @@ -119,30 +158,24 @@ export function ExperimentalModelsTree({ imodel }: { imodel: IModelConnection })
return [];
}
};
}, [imodel]);
}, [props.imodel]);
const modelSource = useTreeModelSource(dataProvider);
const nodeLoader = useTreeNodeLoader(dataProvider, modelSource);
const eventHandler = useMemo(() => new TreeEventHandler({ nodeLoader, modelSource }), [nodeLoader, modelSource]);
const treeModel = useTreeModel(modelSource);

return (
<div className="treewidget">
<div className="treewidget-header">
<h3>{IModelApp.localization.getLocalizedString("Sample:controls.tree")}</h3>
</div>
<div ref={ref} className="filteredTree">
{width && height ? (
<ControlledTree
model={treeModel}
eventsHandler={eventHandler}
nodeLoader={nodeLoader}
selectionMode={SelectionMode.Extended}
iconsEnabled={true}
width={width}
height={height}
/>
) : null}
</div>
<div className="filteredTree">
{props.height && props.width ? (
<ControlledTree
model={treeModel}
eventsHandler={eventHandler}
nodeLoader={nodeLoader}
selectionMode={SelectionMode.Extended}
iconsEnabled={true}
width={props.width}
height={props.height}
/>
) : null}
</div>
);
}
Expand All @@ -153,7 +186,7 @@ function parseTreeNodeItem(node: HierarchyNode): DelayLoadedTreeNodeItem {
}
return {
__internal: node,
id: JSON.stringify(node.key),
id: JSON.stringify([...node.parentKeys, node.key]),
label: PropertyRecord.fromString(node.label, "Label"),
icon: node.extendedData?.imageId,
hasChildren: !!node.children,
Expand Down

0 comments on commit d140061

Please sign in to comment.