Skip to content

Commit ebe056d

Browse files
Collection: hide children of collection until active child (#20392)
* remove use of modals in collections * add parent path to support absolute path generation * add note * make tree load more minimalistic * set type and expand inherited styles * also set title * Update src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts Co-authored-by: Copilot <[email protected]> * create actions should not open as modal * remove unused import * fix router getActivePath * make expand open the collection * setTargetTakeSize to low when Collection parent * expose typeUnique * fix opening collection * remove log * active manager * export * impl active manager * prepare for search param redirects * fixed collapse feature * set routes to undefined * preserveQuery * ensureSlash * make a hard redirect for collections * only if anscenstors are present in data. * not full match anyway * only forceShow on hasCollection * remove umb-section-sidebar-context-menu * rename to isMenu * Update src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace-split-view.element.ts Co-authored-by: Copilot <[email protected]> * Update src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.context.ts Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]>
1 parent 54407a0 commit ebe056d

File tree

20 files changed

+467
-88
lines changed

20 files changed

+467
-88
lines changed

src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/views/design/content-type-design-editor.element.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
115115
private _hasRootGroups = false;
116116

117117
@state()
118-
private _routes: UmbRoute[] = [];
118+
private _routes?: UmbRoute[];
119119

120120
@state()
121121
private _tabs?: Array<UmbPropertyTypeContainerMergedModel>;

src/Umbraco.Web.UI.Client/src/packages/core/router/route/route.context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class UmbRouteContext extends UmbContextBase {
4242
return this.#basePath.getValue();
4343
}
4444
getActivePath() {
45-
return this.getBasePath() + '/' + this.#activeLocalPath;
45+
return this.getBasePath() + '/' + this.#activeLocalPath.getValue();
4646
}
4747

4848
public registerModal(registration: UmbModalRouteRegistration) {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './tree-active-manager.js';
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { expect } from '@open-wc/testing';
2+
import { Observable } from '@umbraco-cms/backoffice/external/rxjs';
3+
import { customElement } from '@umbraco-cms/backoffice/external/lit';
4+
import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api';
5+
import { UmbTreeItemActiveManager } from './tree-active-manager';
6+
7+
@customElement('test-my-controller-host')
8+
class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
9+
10+
describe('UmbTreeItemActiveManager', () => {
11+
let manager: UmbTreeItemActiveManager;
12+
const item = { entityType: 'test', unique: '123' };
13+
const item2 = { entityType: 'test', unique: '456' };
14+
15+
beforeEach(() => {
16+
const hostElement = new UmbTestControllerHostElement();
17+
manager = new UmbTreeItemActiveManager(hostElement);
18+
});
19+
20+
describe('Public API', () => {
21+
describe('properties', () => {
22+
it('has an active property', () => {
23+
expect(manager).to.have.property('active').to.be.an.instanceOf(Observable);
24+
});
25+
});
26+
27+
describe('methods', () => {
28+
it('has an isActive method', () => {
29+
expect(manager).to.have.property('isActive').that.is.a('function');
30+
});
31+
});
32+
});
33+
34+
describe('isActive', () => {
35+
it('checks if an item is active', (done) => {
36+
manager.setActive([item]);
37+
const isActive = manager.isActive(item);
38+
expect(isActive).to.be.an.instanceOf(Observable);
39+
manager.isActive(item).subscribe((value) => {
40+
expect(value).to.be.true;
41+
done();
42+
});
43+
});
44+
});
45+
46+
describe('setActive & getActive', () => {
47+
it('sets and gets the active state', () => {
48+
const active = [item];
49+
manager.setActive(active);
50+
expect(manager.getActive()).to.deep.equal(active);
51+
});
52+
});
53+
54+
describe('removeActiveIfMatch', () => {
55+
it('removes the active state', () => {
56+
const active = [item];
57+
manager.setActive(active);
58+
manager.removeActiveIfMatch(active);
59+
expect(manager.getActive()).to.deep.equal([]);
60+
});
61+
it('does not remove the active state if it does not match', () => {
62+
const active = [item];
63+
manager.setActive(active);
64+
manager.removeActiveIfMatch([item2]);
65+
expect(manager.getActive()).to.deep.equal([item]);
66+
});
67+
});
68+
});
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
2+
import { UmbArrayState, type Observable } from '@umbraco-cms/backoffice/observable-api';
3+
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
4+
5+
/**
6+
* Manages the expansion state of a tree
7+
* @exports
8+
* @class UmbTreeItemActiveManager
9+
* @augments {UmbControllerBase}
10+
*/
11+
export class UmbTreeItemActiveManager extends UmbControllerBase {
12+
#active = new UmbArrayState<UmbEntityModel>([], (x) => x.entityType + x.unique);
13+
readonly active = this.#active.asObservable();
14+
15+
/**
16+
* Checks if an entity is active
17+
* @param {UmbEntityModel} entity The entity to check
18+
* @returns {Observable<boolean>} True if the entity is active
19+
* @memberof UmbTreeItemActiveManager
20+
*/
21+
isActive(entity: UmbEntityModel): Observable<boolean> {
22+
return this.#active.asObservablePart((entities) => {
23+
const index = entities.findIndex((e) => e.entityType === entity.entityType && e.unique === entity.unique);
24+
return index === entities.length;
25+
});
26+
}
27+
28+
/**
29+
* Checks if an descendant entity is active
30+
* @param {UmbEntityModel} entity The entity to check
31+
* @returns {Observable<boolean>} True if a descendant entity is active
32+
* @memberof UmbTreeItemActiveManager
33+
*/
34+
hasActiveDescendants(entity: UmbEntityModel): Observable<boolean> {
35+
return this.#active.asObservablePart((entities) => {
36+
const index = entities.findIndex((e) => e.entityType === entity.entityType && e.unique === entity.unique);
37+
return index > -1 && index < entities.length - 1;
38+
});
39+
}
40+
/**
41+
* Checks if an descendant entity is active
42+
* @param {UmbEntityModel} entity The entity to check
43+
* @returns {boolean} True if a descendant entity is active
44+
* @memberof UmbTreeItemActiveManager
45+
*/
46+
getHasActiveDescendants(entity: UmbEntityModel): boolean {
47+
return this.#active.getValue().some((e) => e.entityType === entity.entityType && e.unique === entity.unique);
48+
}
49+
50+
/**
51+
* Sets the active chain state
52+
* @param {Array<UmbEntityModel>} activeChain The active entries.
53+
* @memberof UmbTreeItemActiveManager
54+
* @returns {void}
55+
*/
56+
setActive(activeChain: Array<UmbEntityModel>): void {
57+
this.#active.setValue(activeChain);
58+
}
59+
60+
/**
61+
* Sets the active chain state
62+
* @param {Array<UmbEntityModel>} activeChain The active entries.
63+
* @memberof UmbTreeItemActiveManager
64+
* @returns {void}
65+
*/
66+
removeActiveIfMatch(activeChain: Array<UmbEntityModel>): void {
67+
const currentChain = this.#active.getValue();
68+
// test if new chain and current chain matches:
69+
// Test length for a start:
70+
if (activeChain.length !== currentChain.length) return;
71+
// test content next:
72+
for (let i = 0; i < activeChain.length; i++) {
73+
if (
74+
activeChain[i].entityType !== currentChain[i].entityType ||
75+
activeChain[i].unique !== currentChain[i].unique
76+
) {
77+
return;
78+
}
79+
}
80+
// TODO: Problem!!!! we are removing the active state, but something is loading that wants to add it...
81+
// then we can remove it all:
82+
this.#active.setValue([]);
83+
}
84+
85+
/**
86+
* Gets the expansion state
87+
* @memberof UmbTreeItemActiveManager
88+
* @returns {Array<UmbEntityModel>} The expansion state
89+
*/
90+
getActive(): Array<UmbEntityModel> {
91+
return this.#active.getValue();
92+
}
93+
}

src/Umbraco.Web.UI.Client/src/packages/core/tree/default/default-tree.context.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { UmbDeprecation, UmbSelectionManager, debounce } from '@umbraco-cms/back
1414
import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
1515
import { umbExtensionsRegistry, type ManifestRepository } from '@umbraco-cms/backoffice/extension-registry';
1616
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
17+
import { UmbTreeItemActiveManager } from '../active-manager/tree-active-manager.js';
1718

1819
export class UmbDefaultTreeContext<
1920
TreeItemType extends UmbTreeItemModel,
@@ -53,6 +54,8 @@ export class UmbDefaultTreeContext<
5354
targetPaginationManager: this.targetPagination,
5455
});
5556

57+
readonly activeManager = new UmbTreeItemActiveManager(this);
58+
5659
#manifest?: ManifestTree;
5760
#repository?: UmbTreeRepository<TreeItemType, TreeRootType>;
5861

src/Umbraco.Web.UI.Client/src/packages/core/tree/default/default-tree.element.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ export class UmbDefaultTreeElement extends UmbLitElement {
4747
@property({ type: Boolean, attribute: false })
4848
foldersOnly?: boolean = false;
4949

50+
@property({ type: Boolean, attribute: false })
51+
isMenu?: boolean = false;
52+
5053
@property({ attribute: false })
5154
selectableFilter: (item: UmbTreeItemModelBase) => boolean = () => true;
5255

@@ -165,7 +168,11 @@ export class UmbDefaultTreeElement extends UmbLitElement {
165168
return html`
166169
<umb-tree-item
167170
.entityType=${this._treeRoot.entityType}
168-
.props=${{ hideActions: this.hideTreeItemActions, item: this._treeRoot }}></umb-tree-item>
171+
.props=${{
172+
hideActions: this.hideTreeItemActions,
173+
item: this._treeRoot,
174+
isMenu: this.isMenu,
175+
}}></umb-tree-item>
169176
`;
170177
}
171178

@@ -180,7 +187,7 @@ export class UmbDefaultTreeElement extends UmbLitElement {
180187
(item) => html`
181188
<umb-tree-item
182189
.entityType=${item.entityType}
183-
.props=${{ hideActions: this.hideTreeItemActions, item }}></umb-tree-item>
190+
.props=${{ hideActions: this.hideTreeItemActions, item, isMenu: this.isMenu }}></umb-tree-item>
184191
`,
185192
)}
186193
${this.#renderLoadNextButton()}

src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './active-manager/index.js';
12
export * from './components/index.js';
23
export * from './constants.js';
34
export * from './data/index.js';

0 commit comments

Comments
 (0)