Skip to content

Commit 3a4bb9e

Browse files
authored
Update to open collapsed containing panel when open widget request is made (#3289)
* Ensure that widgetDef set/get widgetState accounts for collapsed panel. * Update test coverage * rush change * extract-api and add change info to NextVersion.md
1 parent d3e5f09 commit 3a4bb9e

File tree

11 files changed

+248
-7
lines changed

11 files changed

+248
-7
lines changed

common/api/appui-abstract.api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2099,6 +2099,8 @@ export enum UiItemsApplicationAction {
20992099

21002100
// @public
21012101
export class UiItemsManager {
2102+
// @internal
2103+
static clearAllProviders(): void;
21022104
static getBackstageItems(): BackstageItem[];
21032105
static getStatusBarItems(stageId: string, stageUsage: string, stageAppData?: any): CommonStatusBarItem[];
21042106
static getToolbarButtonItems(stageId: string, stageUsage: string, toolbarUsage: ToolbarUsage, toolbarOrientation: ToolbarOrientation, stageAppData?: any): CommonToolbarItem[];
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@itwin/appui-abstract",
5+
"comment": "Provide internal method to clear out all registered item providers for use in unit testing.",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@itwin/appui-abstract"
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@itwin/appui-react",
5+
"comment": "Add the ability to Open a collapsed Panel when a request to Open a widget in a collapsed panel is made.",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@itwin/appui-react"
10+
}

docs/changehistory/NextVersion.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,9 @@ It is now possible to retrieve `Units` from schemas stored in IModels. The new [
8484

8585
UiFramework.setIModelConnection(iModelConnection, true);
8686
```
87+
88+
## AppUI Updates
89+
90+
### WidgetState changes
91+
92+
The property [WidgetDef.state]($appui-react) will now return `WidgetState.Closed` if the widget is in a panel that is collapsed, or the panel size is 0 or undefined. When `WidgetState.Open` is passed to the method [WidgetDef.setWidgetState]($appui-react) the containing panel will also open if it is in a collapsed state.

test-apps/ui-items-providers-test/src/ui/providers/NetworkTracingUiProvider.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ export class NetworkTracingUiProvider implements UiItemsProvider {
166166
public provideWidgets(stageId: string, _stageUsage: string, location: StagePanelLocation,
167167
section?: StagePanelSection): ReadonlyArray<AbstractWidgetProps> {
168168
const widgets: AbstractWidgetProps[] = [];
169-
if (stageId === NetworkTracingFrontstage.stageId && location === StagePanelLocation.Right && section === StagePanelSection.Start) {
169+
if ((stageId === NetworkTracingFrontstage.stageId || stageId === "ui-test-app:no-widget-frontstage") &&
170+
location === StagePanelLocation.Right && section === StagePanelSection.Start) {
170171
/** This widget when only be displayed when there is an element selected. */
171172
const widget: AbstractWidgetProps = {
172173
id: "ui-item-provider-test:elementDataListWidget",

ui/appui-abstract/src/appui-abstract/UiItemsManager.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ export interface UiItemProviderRegisteredEventArgs {
138138
export class UiItemsManager {
139139
private static _registeredUiItemsProviders: Map<string, UiItemsProvider> = new Map<string, UiItemsProvider>();
140140

141+
/** For use in unit testing
142+
* @internal */
143+
public static clearAllProviders() {
144+
UiItemsManager._registeredUiItemsProviders.clear();
145+
}
146+
141147
/** Event raised any time a UiProvider is registered or unregistered. */
142148
public static readonly onUiProviderRegisteredEvent = new BeEvent<(ev: UiItemProviderRegisteredEventArgs) => void>();
143149

ui/appui-abstract/src/test/UiItemsManager.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,15 @@ describe("UiItemsManager", () => {
8888
expect(UiItemsManager.hasRegisteredProviders).to.be.false;
8989
});
9090

91+
it("can clear all providers", () => {
92+
const testUiProvider = new TestUiItemsProvider("TestUiItemsProvider");
93+
expect(UiItemsManager.hasRegisteredProviders).to.be.false;
94+
UiItemsManager.register(testUiProvider);
95+
expect(UiItemsManager.hasRegisteredProviders).to.be.true;
96+
UiItemsManager.clearAllProviders();
97+
expect(UiItemsManager.hasRegisteredProviders).to.be.false;
98+
});
99+
91100
it("if no registered providers no tools are available", () => {
92101
const toolSpecs = UiItemsManager.getToolbarButtonItems("stage", testStageUsage, ToolbarUsage.ContentManipulation, ToolbarOrientation.Horizontal);
93102
expect(toolSpecs.length).to.be.eq(0);

ui/appui-react/src/appui-react/frontstage/FrontstageDef.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,8 +708,13 @@ export class FrontstageDef {
708708
if (isFloatingLocation(location))
709709
return WidgetState.Floating;
710710

711+
let collapsedPanel = false;
712+
if ("side" in location) {
713+
const panel = this.nineZoneState.panels[location.side];
714+
collapsedPanel = panel.collapsed || undefined === panel.size || 0 === panel.size;
715+
}
711716
const widgetContainer = this.nineZoneState.widgets[location.widgetId];
712-
if (widgetDef.id === widgetContainer.activeTabId)
717+
if (widgetDef.id === widgetContainer.activeTabId && !collapsedPanel)
713718
return WidgetState.Open;
714719
else
715720
return WidgetState.Closed;

ui/appui-react/src/appui-react/widget-panels/Frontstage.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ export function addWidgets(state: NineZoneState, widgets: ReadonlyArray<WidgetDe
278278

279279
const activeWidget = visibleWidgets.find((widget) => widget.isActive);
280280
const minimized = !activeWidget;
281+
// istanbul ignore else
281282
if (activeWidget?.defaultState !== WidgetState.Floating) {
282283
state = addPanelWidget(state, side, widgetId, tabs, {
283284
activeTabId: activeWidget ? activeWidget.id : tabs[0],
@@ -638,6 +639,7 @@ const stateVersion = 11; // this needs to be bumped when NineZoneState is change
638639
export function initializeNineZoneState(frontstageDef: FrontstageDef): NineZoneState {
639640
let nineZone = defaultNineZone;
640641
nineZone = produce(nineZone, (stateDraft) => {
642+
// istanbul ignore next
641643
if (!FrontstageManager.nineZoneSize)
642644
return;
643645
stateDraft.size = {
@@ -841,6 +843,16 @@ export const setWidgetState = produce((
841843
const widget = nineZone.widgets[location.widgetId];
842844
widget.minimized = false;
843845
widget.activeTabId = id;
846+
// ensure panel containing widget is not collapsed
847+
// istanbul ignore else
848+
if ("side" in location) {
849+
const panel = nineZone.panels[location.side];
850+
panel.collapsed && (panel.collapsed = false);
851+
// istanbul ignore next
852+
if (undefined === panel.size || 0 === panel.size) {
853+
panel.size = panel.minSize ?? 200;
854+
}
855+
}
844856
} else if (state === WidgetState.Closed) {
845857
const id = widgetDef.id;
846858
let location = findTab(nineZone, id);

ui/appui-react/src/test/frontstage/FrontstageDef.test.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { expect } from "chai";
77
import * as React from "react";
88
import * as sinon from "sinon";
9+
import produce from "immer";
910
import { MockRender } from "@itwin/core-frontend";
1011
import { CoreTools, Frontstage, FRONTSTAGE_SETTINGS_NAMESPACE, FrontstageDef, FrontstageManager, FrontstageProps, FrontstageProvider, getFrontstageStateSettingName, StagePanelDef, UiFramework, WidgetDef } from "../../appui-react";
1112
import TestUtils, { storageMock } from "../TestUtils";
@@ -323,12 +324,15 @@ describe("float and dock widget", () => {
323324

324325
it("panel widget should popout", async () => {
325326
let state = createNineZoneState({ size: { height: 1000, width: 1600 } });
326-
state = addPanelWidget(state, "right", "rightStart", ["t1"], { minimized: true });
327+
state = addPanelWidget(state, "left", "leftStart", ["t1"], { minimized: true });
327328
state = addPanelWidget(state, "right", "rightMiddle", ["t2", "t4"], { activeTabId: "t2" });
328329
state = addPanelWidget(state, "right", "rightEnd", ["t3"]);
329330
state = addTab(state, "t1", { preferredPopoutWidgetSize: { width: 99, height: 99, x: 99, y: 99 } });
330331
state = addTab(state, "t2");
331332
state = addTab(state, "t3");
333+
state = produce(state, (draft) => {
334+
draft.panels.right.size = 300;
335+
});
332336

333337
const frontstageDef = new FrontstageDef();
334338
const nineZoneStateSetter = sinon.spy();
@@ -354,6 +358,7 @@ describe("float and dock widget", () => {
354358
.onFirstCall().returns(t1);
355359
findWidgetDefGetter.returns(t2);
356360

361+
expect(frontstageDef.getWidgetCurrentState(t1)).to.eql(WidgetState.Closed);
357362
expect(frontstageDef.getWidgetCurrentState(t2)).to.eql(WidgetState.Open);
358363
expect(frontstageDef.getWidgetCurrentState(t4)).to.eql(WidgetState.Closed);
359364

0 commit comments

Comments
 (0)