app/javascript/components/PageTree/Node.tsx
import { Fragment, useRef } from "react";
import usePageTreeContext from "./usePageTreeContext";
import * as Tree from "./tree";
import { addChild, updatePage, visibleChildNodes } from "./usePageTree";
import Button from "./Button";
import CollapseArrow from "./CollapseArrow";
import CollapsedLabel from "./CollapsedLabel";
import PageName from "./PageName";
import StatusLabel from "./StatusLabel";
import EditPageName from "./EditPageName";
type Props = {
id: Tree.Id;
dragging?: Tree.Id;
onDragStart?: (
id: number,
element: HTMLDivElement,
evt: React.MouseEvent | React.TouchEvent
) => void;
};
export const paddingLeft = 20;
export default function Node(props: Props) {
const { state, dispatch } = usePageTreeContext();
const { id, dragging, onDragStart } = props;
const { dir, locale } = state;
const node = state.nodes[id];
const page = node.record;
const name = page.blocks.name[locale];
const innerRef = useRef<HTMLDivElement>();
const classNames = ["node"];
if (id === dragging) {
classNames.push("placeholder");
}
const pageClassNames = ["page"];
let iconClass = "fa-regular fa-file icon";
if (!("root" in page)) {
pageClassNames.push(`status-${page.status}`);
if (page.news_page) {
iconClass = "fa-regular fa-file-lines page-icon";
} else if (page.pinned) {
iconClass = "fa-regular fa-flag page-icon";
}
}
const permitted = (action: string): boolean => {
return page.permissions && page.permissions.indexOf(action) !== -1;
};
const handleAddChild = () => {
addChild(state, id, dispatch);
};
const handleDragStart = (evt: React.MouseEvent | React.TouchEvent) => {
if (permitted("edit") && !page.editing && onDragStart) {
onDragStart(id, innerRef.current, evt);
}
};
const handleDelete = () => {
if (confirm("Are you sure you want to delete this page?")) {
updatePage(state, id, dispatch, { status: 4 });
}
};
const handleEdit = () => {
dispatch({ type: "update", id: id, payload: { editing: true } });
};
const handleToggleStatus = () => {
if ("status" in page && page.status != 2) {
updatePage(state, id, dispatch, { status: 2 });
} else {
updatePage(state, id, dispatch, { status: 3 });
}
};
return (
<div className={classNames.join(" ")}>
<div className="inner" ref={innerRef} onMouseDown={handleDragStart}>
<CollapseArrow id={id} />
{!page.editing && (
<div className={pageClassNames.join(" ")}>
<i className={iconClass}></i>
<PageName
name={name}
dir={dir}
locale={locale}
editUrl={
"id" in page &&
permitted("edit") &&
`/admin/${locale}/pages/${page.id}/edit`
}
/>
{"status" in page && <StatusLabel status={page.status} />}
<CollapsedLabel id={id} />
<span className="actions">
{!("root" in page) && (
<Fragment>
{permitted("edit") && (
<Button
label={page.status != 2 ? "Publish" : "Hide"}
className="toggle-status"
icon={page.status != 2 ? "check" : "ban"}
onClick={handleToggleStatus}
/>
)}
{permitted("edit") && (
<Button
label="Rename"
className="edit"
icon="pencil"
onClick={handleEdit}
/>
)}
{permitted("edit") && (
<Button
label="Delete"
className="delete"
icon="trash"
onClick={handleDelete}
/>
)}
</Fragment>
)}
{permitted("create") && (
<Button
label="Add child"
className="add"
icon="plus"
onClick={handleAddChild}
/>
)}
</span>
</div>
)}
{page.editing && <EditPageName id={id} />}
</div>
{!node.collapsed && visibleChildNodes(state, id).length > 0 && (
<Fragment>
<div className="children" style={{ paddingLeft: `${paddingLeft}px` }}>
{visibleChildNodes(state, id).map((childId) => {
return <Node {...props} id={childId} key={childId} />;
})}
</div>
{permitted("create") && (
<Button
label="Add page here"
className="add add-inline transparent"
icon="plus"
onClick={handleAddChild}
/>
)}
</Fragment>
)}
</div>
);
}