2019-09-06 12:58:44 +00:00
|
|
|
interface IBaseMenuItem<TMenuData = {}, TValue = string> {
|
2019-06-19 14:40:52 +00:00
|
|
|
label: React.ReactNode;
|
2019-09-06 12:58:44 +00:00
|
|
|
value?: TValue;
|
2019-06-19 14:40:52 +00:00
|
|
|
data: TMenuData | null;
|
|
|
|
}
|
2019-09-06 12:58:44 +00:00
|
|
|
export type IFlatMenuItem<TMenuData = {}, TValue = string> = IBaseMenuItem<
|
|
|
|
TMenuData,
|
|
|
|
TValue
|
|
|
|
> & {
|
2019-06-19 14:40:52 +00:00
|
|
|
id: string;
|
|
|
|
parent: string | null;
|
|
|
|
sort: number;
|
|
|
|
};
|
2019-09-06 12:58:44 +00:00
|
|
|
export type IMenuItem<TMenuData = {}, TValue = string> = IBaseMenuItem<
|
|
|
|
TMenuData,
|
|
|
|
TValue
|
|
|
|
> & {
|
|
|
|
children: Array<IMenuItem<TMenuData, TValue>>;
|
2019-06-19 14:40:52 +00:00
|
|
|
};
|
2019-09-06 12:58:44 +00:00
|
|
|
export type IMenu<TMenuData = {}, TValue = string> = Array<
|
|
|
|
IMenuItem<TMenuData, TValue>
|
|
|
|
>;
|
|
|
|
export type IFlatMenu<TMenuData = {}, TValue = string> = Array<
|
|
|
|
IFlatMenuItem<TMenuData, TValue>
|
|
|
|
>;
|
|
|
|
|
|
|
|
export function validateMenuOptions<TMenuData = {}, TValue = string>(
|
|
|
|
menu: IMenu<TMenuData, TValue>
|
2019-06-19 14:40:52 +00:00
|
|
|
): boolean {
|
2019-09-06 12:58:44 +00:00
|
|
|
const values: TValue[] = toFlat(menu)
|
2019-06-19 14:40:52 +00:00
|
|
|
.map(menuItem => menuItem.value)
|
|
|
|
.filter(value => value !== undefined);
|
|
|
|
const uniqueValues = Array.from(new Set(values));
|
|
|
|
return uniqueValues.length === values.length;
|
|
|
|
}
|
|
|
|
|
2019-09-06 12:58:44 +00:00
|
|
|
function _getMenuItemByPath<TMenuData = {}, TValue = string>(
|
|
|
|
menuItem: IMenuItem<TMenuData, TValue>,
|
2019-06-19 14:40:52 +00:00
|
|
|
path: number[]
|
2019-09-06 12:58:44 +00:00
|
|
|
): IMenuItem<TMenuData, TValue> {
|
2019-06-19 14:40:52 +00:00
|
|
|
if (path.length === 0) {
|
|
|
|
return menuItem;
|
|
|
|
}
|
|
|
|
return _getMenuItemByPath(menuItem.children[path[0]], path.slice(1));
|
|
|
|
}
|
|
|
|
|
2019-09-06 12:58:44 +00:00
|
|
|
export function getMenuItemByPath<TMenuData = {}, TValue = string>(
|
|
|
|
menu: IMenu<TMenuData, TValue>,
|
2019-06-19 14:40:52 +00:00
|
|
|
path: number[]
|
2019-09-06 12:58:44 +00:00
|
|
|
): IMenuItem<TMenuData, TValue> {
|
2019-06-19 14:40:52 +00:00
|
|
|
return _getMenuItemByPath(menu[path[0]], path.slice(1));
|
|
|
|
}
|
|
|
|
|
2019-09-06 12:58:44 +00:00
|
|
|
export function getMenuItemByValue<TMenuData = {}, TValue = string>(
|
|
|
|
menu: IMenu<TMenuData, TValue>,
|
|
|
|
value: TValue
|
|
|
|
): IMenuItem<TMenuData, TValue> {
|
2019-06-19 14:40:52 +00:00
|
|
|
const flatMenu = toFlat(menu);
|
2019-09-06 12:58:44 +00:00
|
|
|
const flatMenuItem: IFlatMenuItem<TMenuData, TValue> = flatMenu.find(
|
2019-06-19 14:40:52 +00:00
|
|
|
menuItem => menuItem.value === value
|
|
|
|
);
|
|
|
|
|
2019-09-06 12:58:44 +00:00
|
|
|
if (flatMenuItem === undefined) {
|
|
|
|
throw new Error(`Value ${value} does not exist in menu`);
|
|
|
|
}
|
|
|
|
|
2019-06-19 14:40:52 +00:00
|
|
|
return _fromFlat(flatMenu, flatMenuItem);
|
|
|
|
}
|
|
|
|
|
2019-09-06 12:58:44 +00:00
|
|
|
function _walkToMenuItem<TMenuData = {}, TValue = string>(
|
|
|
|
menuItem: IMenuItem<TMenuData, TValue>,
|
2019-06-19 14:40:52 +00:00
|
|
|
path: number[]
|
2019-09-06 12:58:44 +00:00
|
|
|
): IMenu<TMenuData, TValue> {
|
2019-06-19 14:40:52 +00:00
|
|
|
const node = menuItem.children[path[0]];
|
|
|
|
|
|
|
|
if (path.length === 1) {
|
|
|
|
return [node];
|
|
|
|
}
|
|
|
|
|
|
|
|
return [node, ..._walkToMenuItem(node, path.slice(1))];
|
|
|
|
}
|
|
|
|
|
2019-09-06 12:58:44 +00:00
|
|
|
export function walkToMenuItem<TMenuData = {}, TValue = string>(
|
|
|
|
menu: IMenu<TMenuData, TValue>,
|
2019-06-19 14:40:52 +00:00
|
|
|
path: number[]
|
2019-09-06 12:58:44 +00:00
|
|
|
): IMenu<TMenuData, TValue> {
|
2019-06-19 14:40:52 +00:00
|
|
|
const walkByNode = menu[path[0]];
|
|
|
|
return [walkByNode, ..._walkToMenuItem(walkByNode, path.slice(1))];
|
|
|
|
}
|
|
|
|
|
2019-09-06 12:58:44 +00:00
|
|
|
function _walkToRoot<TMenuData = {}, TValue = string>(
|
|
|
|
flatMenu: IFlatMenu<TMenuData, TValue>,
|
2019-06-19 14:40:52 +00:00
|
|
|
parent: string
|
2019-09-06 12:58:44 +00:00
|
|
|
): IFlatMenu<TMenuData, TValue> {
|
2019-06-19 14:40:52 +00:00
|
|
|
const menuItem = flatMenu.find(menuItem => menuItem.id === parent);
|
|
|
|
|
|
|
|
if (menuItem.parent === null) {
|
|
|
|
return [menuItem];
|
|
|
|
}
|
|
|
|
|
|
|
|
return [menuItem, ..._walkToRoot(flatMenu, menuItem.parent)];
|
|
|
|
}
|
2019-09-06 12:58:44 +00:00
|
|
|
export function walkToRoot<TMenuData = {}, TValue = string>(
|
|
|
|
menu: IMenu<TMenuData, TValue>,
|
|
|
|
value: TValue
|
|
|
|
): IMenu<TMenuData, TValue> {
|
2019-06-19 14:40:52 +00:00
|
|
|
const flatMenu = toFlat(menu);
|
|
|
|
const menuItem = flatMenu.find(menuItem => menuItem.value === value);
|
|
|
|
|
|
|
|
return (menuItem.parent === null
|
|
|
|
? [menuItem]
|
|
|
|
: [menuItem, ..._walkToRoot(flatMenu, menuItem.parent)]
|
|
|
|
).map(flatMenuItem => _fromFlat(flatMenu, flatMenuItem));
|
|
|
|
}
|
|
|
|
|
2019-09-06 12:58:44 +00:00
|
|
|
function _toFlat<TMenuData = {}, TValue = string>(
|
|
|
|
menuItem: IMenuItem<TMenuData, TValue>,
|
2019-06-19 14:40:52 +00:00
|
|
|
sort: number,
|
|
|
|
parent: string
|
2019-09-06 12:58:44 +00:00
|
|
|
): IFlatMenu<TMenuData, TValue> {
|
2019-06-19 14:40:52 +00:00
|
|
|
const id = parent ? [parent, sort].join(":") : sort.toString();
|
2019-09-06 12:58:44 +00:00
|
|
|
const flatMenuItem: IFlatMenuItem<TMenuData, TValue> = {
|
2019-06-19 14:40:52 +00:00
|
|
|
data: menuItem.data,
|
|
|
|
id,
|
|
|
|
label: menuItem.label,
|
|
|
|
parent,
|
|
|
|
sort,
|
|
|
|
value: menuItem.value
|
|
|
|
};
|
|
|
|
return [
|
|
|
|
flatMenuItem,
|
|
|
|
...menuItem.children
|
|
|
|
.map((child, childIndex) => _toFlat(child, childIndex, id))
|
2019-09-06 12:58:44 +00:00
|
|
|
.reduce((acc, curr) => [...acc, ...curr], [] as IFlatMenu<
|
|
|
|
TMenuData,
|
|
|
|
TValue
|
|
|
|
>)
|
2019-06-19 14:40:52 +00:00
|
|
|
];
|
|
|
|
}
|
2019-09-06 12:58:44 +00:00
|
|
|
export function toFlat<TMenuData = {}, TValue = string>(
|
|
|
|
menu: IMenu<TMenuData, TValue>
|
|
|
|
): IFlatMenu<TMenuData, TValue> {
|
2019-06-19 14:40:52 +00:00
|
|
|
return menu
|
|
|
|
.map((menuItem, menuItemIndex) => _toFlat(menuItem, menuItemIndex, null))
|
2019-09-06 12:58:44 +00:00
|
|
|
.reduce((acc, curr) => [...acc, ...curr], [] as IFlatMenu<
|
|
|
|
TMenuData,
|
|
|
|
TValue
|
|
|
|
>);
|
2019-06-19 14:40:52 +00:00
|
|
|
}
|
|
|
|
|
2019-09-06 12:58:44 +00:00
|
|
|
function _fromFlat<TMenuData = {}, TValue = string>(
|
|
|
|
menu: IFlatMenu<TMenuData, TValue>,
|
|
|
|
flatMenuItem: IFlatMenuItem<TMenuData, TValue>
|
|
|
|
): IMenuItem<TMenuData, TValue> {
|
|
|
|
const children: Array<IMenuItem<TMenuData, TValue>> = menu
|
2019-06-19 14:40:52 +00:00
|
|
|
.filter(menuItem => menuItem.parent === flatMenuItem.id)
|
|
|
|
.map(menuItem => _fromFlat(menu, menuItem));
|
|
|
|
|
|
|
|
return {
|
|
|
|
children,
|
|
|
|
data: flatMenuItem.data,
|
|
|
|
label: flatMenuItem.label,
|
|
|
|
value: flatMenuItem.value
|
|
|
|
};
|
|
|
|
}
|
2019-09-06 12:58:44 +00:00
|
|
|
export function fromFlat<TMenuData = {}, TValue = string>(
|
|
|
|
menu: IFlatMenu<TMenuData, TValue>
|
|
|
|
): IMenu<TMenuData, TValue> {
|
2019-06-19 14:40:52 +00:00
|
|
|
return menu
|
|
|
|
.filter(menuItem => menuItem.parent === null)
|
|
|
|
.map(menuItem => _fromFlat(menu, menuItem));
|
|
|
|
}
|
|
|
|
|
2019-09-06 12:58:44 +00:00
|
|
|
export function isLeaf<TMenuData = {}, TValue = string>(
|
|
|
|
menuItem: IMenuItem<TMenuData, TValue>
|
2019-06-19 14:40:52 +00:00
|
|
|
): boolean {
|
|
|
|
return menuItem.children.length === 0;
|
|
|
|
}
|