1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-09 20:13:42 +00:00

[EC-63] Implement breadcrumb component (#3762)

* [EC-63] feat: scaffold breadcrumb module

* [EC-63] feat: add first very basic structure

* [EC-63] feat: dynamically rendered crumbs with styling

* [EC-63] feat: implement overflow logic

* [EC-63] feat: hide overflow and show ellipsis

* [EC-63] feat: fully working with links

* [EC-63] feat: add support for only showing last crumb

* [EC-63] chore: fix missing template

* [EC-63] chore: refactor and add test case

* [EC-63] refactor: change parent type to treenode

* [EC-63] feat: add breadcrumbs to org vault

* [EC-63] feat: add links to breadcrumbs (dont work yet)

* [EC-63] feat: add support for click handler in breadcrumbs

* [EC-63] feat: working breadcrumb links

* [EC-63] feat: add collections group head

* [EC-63] feat: add breadcrumbs to personal vault

* [EC-63] feat: use icon button

* [EC-63] feat: use small icon button

* [EC-63] fix: add margin to breadcrumb links

The reason for this fix is that the bitIconButton used to open the overflow menu is much taller than the rest of the elements in the list. This causes the whole component to grow and shrink depending on if it contains too many breadcrumbs or not. In the web vault this causes the cipher list to jump up and down while navigating. This increases the height of the entire component so that the icon button no longer affects it.

* [EC-63] fix: tests using wrong parent

* [EC-63] feat: use ngIf instead of else

* [EC-63] refactor: attempt to improve tree node factory readability
This commit is contained in:
Andreas Coroiu
2022-12-21 18:01:46 +01:00
committed by GitHub
parent c5ae076018
commit ef20ee1882
16 changed files with 431 additions and 59 deletions

View File

@@ -1,56 +1,27 @@
import { TreeNode } from "../models/domain/tree-node";
import { ITreeNodeObject, TreeNode } from "../models/domain/tree-node";
import { ServiceUtils } from "./serviceUtils";
type FakeObject = { id: string; name: string };
describe("serviceUtils", () => {
type fakeObject = { id: string; name: string };
let nodeTree: TreeNode<fakeObject>[];
let nodeTree: TreeNode<FakeObject>[];
beforeEach(() => {
nodeTree = [
{
parent: null,
node: { id: "1", name: "1" },
children: [
{
parent: { id: "1", name: "1" },
node: { id: "1.1", name: "1.1" },
children: [
{
parent: { id: "1.1", name: "1.1" },
node: { id: "1.1.1", name: "1.1.1" },
children: [],
},
],
},
{
parent: { id: "1", name: "1" },
node: { id: "1.2", name: "1.2" },
children: [],
},
],
},
{
parent: null,
node: { id: "2", name: "2" },
children: [
{
parent: { id: "2", name: "2" },
node: { id: "2.1", name: "2.1" },
children: [],
},
],
},
{
parent: null,
node: { id: "3", name: "3" },
children: [],
},
createTreeNode({ id: "1", name: "1" }, [
createTreeNode({ id: "1.1", name: "1.1" }, [
createTreeNode({ id: "1.1.1", name: "1.1.1" }),
]),
createTreeNode({ id: "1.2", name: "1.2" }),
])(null),
createTreeNode({ id: "2", name: "2" }, [createTreeNode({ id: "2.1", name: "2.1" })])(null),
createTreeNode({ id: "3", name: "3" }, [])(null),
];
});
describe("nestedTraverse", () => {
it("should traverse a tree and add a node at the correct position given a valid path", () => {
const nodeToBeAdded: fakeObject = { id: "1.2.1", name: "1.2.1" };
const nodeToBeAdded: FakeObject = { id: "1.2.1", name: "1.2.1" };
const path = ["1", "1.2", "1.2.1"];
ServiceUtils.nestedTraverse(nodeTree, 0, path, nodeToBeAdded, null, "/");
@@ -58,7 +29,7 @@ describe("serviceUtils", () => {
});
it("should combine the path for missing nodes and use as the added node name given an invalid path", () => {
const nodeToBeAdded: fakeObject = { id: "blank", name: "blank" };
const nodeToBeAdded: FakeObject = { id: "blank", name: "blank" };
const path = ["3", "3.1", "3.1.1"];
ServiceUtils.nestedTraverse(nodeTree, 0, path, nodeToBeAdded, null, "/");
@@ -82,3 +53,20 @@ describe("serviceUtils", () => {
});
});
});
type TreeNodeFactory<T extends ITreeNodeObject> = (
obj: T,
children?: TreeNodeFactoryWithoutParent<T>[]
) => TreeNodeFactoryWithoutParent<T>;
type TreeNodeFactoryWithoutParent<T extends ITreeNodeObject> = (
parent?: TreeNode<T>
) => TreeNode<T>;
const createTreeNode: TreeNodeFactory<FakeObject> =
(obj, children = []) =>
(parent) => {
const node = new TreeNode<FakeObject>(obj, parent, obj.name, obj.id);
node.children = children.map((childFunc) => childFunc(node));
return node;
};

View File

@@ -15,7 +15,7 @@ export class ServiceUtils {
partIndex: number,
parts: string[],
obj: ITreeNodeObject,
parent: ITreeNodeObject,
parent: TreeNode<ITreeNodeObject> | undefined,
delimiter: string
) {
if (parts.length <= partIndex) {
@@ -40,7 +40,7 @@ export class ServiceUtils {
partIndex + 1,
parts,
obj,
nodeTree[i].node,
nodeTree[i],
delimiter
);
return;

View File

@@ -1,9 +1,9 @@
export class TreeNode<T extends ITreeNodeObject> {
parent: T;
node: T;
parent: TreeNode<T>;
children: TreeNode<T>[] = [];
constructor(node: T, parent: T, name?: string, id?: string) {
constructor(node: T, parent: TreeNode<T>, name?: string, id?: string) {
this.parent = parent;
this.node = node;
if (name) {