bazarr/frontend/src/Sidebar/items.tsx

180 lines
4.6 KiB
TypeScript

import { IconDefinition } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { FunctionComponent, useContext, useMemo } from "react";
import { Badge, Collapse, ListGroupItem } from "react-bootstrap";
import { NavLink } from "react-router-dom";
import { siteChangeSidebar } from "../@redux/actions";
import { useReduxAction, useReduxStore } from "../@redux/hooks/base";
import { SidebarToggleContext } from "../App";
import {
BadgeProvider,
ChildBadgeProvider,
CollapseItemType,
LinkItemType,
} from "./types";
export const HiddenKeysContext = React.createContext<string[]>([]);
export const BadgesContext = React.createContext<BadgeProvider>({});
function useToggleSidebar() {
return useReduxAction(siteChangeSidebar);
}
function useSidebarKey() {
return useReduxStore((s) => s.site.sidebar);
}
export const LinkItem: FunctionComponent<LinkItemType> = ({
link,
name,
icon,
}) => {
const badges = useContext(BadgesContext);
const toggle = useContext(SidebarToggleContext);
const badgeValue = useMemo(() => {
let badge: Nullable<number> = null;
if (name in badges) {
let item = badges[name];
if (typeof item === "number") {
badge = item;
}
}
return badge;
}, [badges, name]);
return (
<NavLink
activeClassName="sb-active"
className="list-group-item list-group-item-action sidebar-button"
to={link}
onClick={toggle}
>
<DisplayItem
badge={badgeValue ?? undefined}
name={name}
icon={icon}
></DisplayItem>
</NavLink>
);
};
export const CollapseItem: FunctionComponent<CollapseItemType> = ({
icon,
name,
children,
}) => {
const badges = useContext(BadgesContext);
const hiddenKeys = useContext(HiddenKeysContext);
const toggleSidebar = useContext(SidebarToggleContext);
const sidebarKey = useSidebarKey();
const updateSidebar = useToggleSidebar();
const [badgeValue, childValue] = useMemo<
[Nullable<number>, Nullable<ChildBadgeProvider>]
>(() => {
let badge: Nullable<number> = null;
let child: Nullable<ChildBadgeProvider> = null;
if (name in badges) {
const item = badges[name];
if (typeof item === "number") {
badge = item;
} else if (typeof item === "object") {
badge = 0;
child = item;
for (const it in item) {
badge += item[it];
}
}
}
return [badge, child];
}, [badges, name]);
const active = useMemo(() => sidebarKey === name, [sidebarKey, name]);
const collapseBoxClass = useMemo(
() => `sidebar-collapse-box ${active ? "active" : ""}`,
[active]
);
const childrenElems = useMemo(
() =>
children
.filter((v) => !hiddenKeys.includes(v.hiddenKey ?? ""))
.map((ch) => {
let badge: Nullable<number> = null;
if (childValue && ch.name in childValue) {
badge = childValue[ch.name];
}
return (
<NavLink
key={ch.name}
activeClassName="sb-active"
className="list-group-item list-group-item-action sidebar-button sb-collapse"
to={ch.link}
onClick={toggleSidebar}
>
<DisplayItem
badge={badge === 0 ? undefined : badge ?? undefined}
name={ch.name}
></DisplayItem>
</NavLink>
);
}),
[children, hiddenKeys, childValue, toggleSidebar]
);
if (childrenElems.length === 0) {
return null;
}
return (
<div className={collapseBoxClass}>
<ListGroupItem
action
className="sidebar-button"
onClick={() => {
if (active) {
updateSidebar("");
} else {
updateSidebar(name);
}
}}
>
<DisplayItem
badge={badgeValue === 0 ? undefined : badgeValue ?? undefined}
icon={icon}
name={name}
></DisplayItem>
</ListGroupItem>
<Collapse in={active}>
<div className="sidebar-collapse">{childrenElems}</div>
</Collapse>
</div>
);
};
interface DisplayProps {
name: string;
icon?: IconDefinition;
badge?: number;
}
const DisplayItem: FunctionComponent<DisplayProps> = ({
name,
icon,
badge,
}) => (
<React.Fragment>
{icon && (
<FontAwesomeIcon size="1x" className="icon" icon={icon}></FontAwesomeIcon>
)}
<span className="d-flex flex-grow-1 justify-content-between">
{name} <Badge variant="secondary">{badge}</Badge>
</span>
</React.Fragment>
);