430 lines
11 KiB
TypeScript
430 lines
11 KiB
TypeScript
import {
|
|
ControlOutlined,
|
|
MessageOutlined,
|
|
QuestionCircleOutlined,
|
|
SettingOutlined,
|
|
SnippetsOutlined,
|
|
TeamOutlined,
|
|
WalletOutlined,
|
|
} from "@ant-design/icons";
|
|
import { Divider, Flex, Form, Menu, Select, Typography } from "antd";
|
|
import { useEffect, useState } from "react";
|
|
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
|
import { useDispatch, useSelector } from "react-redux";
|
|
import {
|
|
setIsSideMenuCollapsed,
|
|
setSideMenuComponentFirstRender,
|
|
sideMenuComponentFirstRender,
|
|
} from "./sideMenuSlice";
|
|
import { ItemType, MenuItemType } from "antd/es/menu/interface";
|
|
import { BreakpointLgWidth, Constants } from "core/utils/utils";
|
|
import Search from "antd/es/input/Search";
|
|
import { MyContainer } from "shared/components/MyContainer";
|
|
import {
|
|
addLessonContent,
|
|
currentLessonId,
|
|
lessonState,
|
|
} from "features/Lessons/LessonPageEditor/lessonPageEditorSlice";
|
|
import { Component, componentsGroups } from "features/Lessons/components";
|
|
import { darkMode, logoUrl } from "core/reducers/appSlice";
|
|
import { DndContext, DragOverlay, useDraggable } from "@dnd-kit/core";
|
|
import { createPortal } from "react-dom";
|
|
import { LessonState } from "core/types/lesson";
|
|
import { useForm } from "antd/es/form/Form";
|
|
import {
|
|
useAddLessonContentMutation,
|
|
useUpdateLessonStateMutation,
|
|
} from "core/services/lessons";
|
|
|
|
export function SideMenuContent() {
|
|
const location = useLocation();
|
|
const [selectedKeys, setSelectedKeys] = useState("/");
|
|
const [openKeys, setOpenKeys] = useState([""]);
|
|
|
|
const dispatch = useDispatch();
|
|
const componentFirstRender = useSelector(sideMenuComponentFirstRender);
|
|
const appLogoUrl = useSelector(logoUrl);
|
|
|
|
const navigate = useNavigate();
|
|
|
|
const getFirstMenuItems = (): ItemType<MenuItemType>[] => {
|
|
let items: ItemType<MenuItemType>[] = [];
|
|
|
|
// overview
|
|
|
|
let overviewGroup: ItemType<MenuItemType> = {
|
|
key: "overviewGroup",
|
|
label: "OVERVIEW",
|
|
type: "group",
|
|
children: [],
|
|
};
|
|
|
|
if (overviewGroup.children) {
|
|
overviewGroup.children.push({
|
|
key: Constants.ROUTE_PATHS.LESSIONS.ROOT,
|
|
label: "Lessons",
|
|
icon: <SnippetsOutlined />,
|
|
});
|
|
}
|
|
|
|
items.push(overviewGroup);
|
|
|
|
// organization
|
|
|
|
let organizationGroup: ItemType<MenuItemType> = {
|
|
key: "organizationGroup",
|
|
label: "ORGANIZATION",
|
|
type: "group",
|
|
children: [],
|
|
};
|
|
|
|
if (organizationGroup.children) {
|
|
organizationGroup.children.push({
|
|
key: Constants.ROUTE_PATHS.ORGANIZATION_TEAM,
|
|
label: "Team",
|
|
icon: <TeamOutlined />,
|
|
});
|
|
|
|
organizationGroup.children.push({
|
|
key: Constants.ROUTE_PATHS.ORGANIZATION_ROLES,
|
|
label: "Roles",
|
|
icon: <ControlOutlined />,
|
|
});
|
|
|
|
organizationGroup.children.push({
|
|
key: Constants.ROUTE_PATHS.ORGANIZATION_SETTINGS,
|
|
label: "Settings",
|
|
icon: <SettingOutlined />,
|
|
});
|
|
}
|
|
|
|
items.push(organizationGroup);
|
|
|
|
return items;
|
|
};
|
|
|
|
const getSecondMenuItems = (): ItemType<MenuItemType>[] => {
|
|
let items: ItemType<MenuItemType>[] = [];
|
|
|
|
// support
|
|
|
|
items.push({
|
|
key: Constants.ROUTE_PATHS.WHATS_NEW,
|
|
label: "What's New",
|
|
icon: <QuestionCircleOutlined />,
|
|
});
|
|
|
|
// feedback
|
|
|
|
items.push({
|
|
key: Constants.ROUTE_PATHS.SUGGEST_FEATURE,
|
|
label: "Suggest a Feature",
|
|
icon: <MessageOutlined />,
|
|
});
|
|
|
|
// payment plan
|
|
|
|
items.push({
|
|
key: Constants.ROUTE_PATHS.CONTACT_SUPPORT,
|
|
label: "Contact Support",
|
|
icon: <WalletOutlined />,
|
|
});
|
|
|
|
return items;
|
|
};
|
|
|
|
useEffect(() => {
|
|
const pathname = location.pathname;
|
|
|
|
setSelectedKeys(pathname);
|
|
|
|
let path = pathname.split("/");
|
|
|
|
if (path.length > 2) {
|
|
// /store/:storeId/:subPage - open the store menu
|
|
setOpenKeys([`/${path[1]}/${path[2]}`]);
|
|
}
|
|
|
|
// auto close sideMenu on mobile
|
|
// this will prevent to auto close sideMenu on first render as the useEffects will be called after the first render
|
|
if (componentFirstRender) {
|
|
dispatch(setSideMenuComponentFirstRender(false));
|
|
} else if (document.body.clientWidth < BreakpointLgWidth) {
|
|
dispatch(setIsSideMenuCollapsed(true));
|
|
}
|
|
}, [location.pathname]);
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
flexDirection: "column",
|
|
height: "100%",
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
justifyContent: "space-between",
|
|
overflowY: "hidden",
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
alignItems: "center",
|
|
padding: 10,
|
|
}}
|
|
></div>
|
|
|
|
<div>
|
|
<Flex justify="center" style={{ paddingBottom: 24, width: "100%" }}>
|
|
<img
|
|
src={`${Constants.STATIC_CONTENT_ADDRESS}${appLogoUrl}`}
|
|
alt="logo"
|
|
style={{ height: 80 }}
|
|
/>
|
|
</Flex>
|
|
|
|
<Menu
|
|
mode="inline"
|
|
onClick={(item) => navigate(item.key)}
|
|
theme="light"
|
|
selectedKeys={[selectedKeys]}
|
|
items={getFirstMenuItems()}
|
|
openKeys={openKeys}
|
|
onOpenChange={(openKeys) =>
|
|
setOpenKeys(
|
|
openKeys[openKeys.length - 1]
|
|
? [openKeys[openKeys.length - 1]]
|
|
: []
|
|
)
|
|
}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<Divider style={{ margin: 0 }} />
|
|
|
|
<Menu
|
|
selectable={true}
|
|
selectedKeys={[selectedKeys]}
|
|
mode="vertical"
|
|
onClick={(item) => navigate(item.key)}
|
|
items={getSecondMenuItems()}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function SideMenuEditorContent() {
|
|
// create is dragging useState
|
|
const [isDragging, setIsDragging] = useState<String | null>(null);
|
|
const { lessonId } = useParams();
|
|
|
|
const [form] = useForm();
|
|
|
|
const lnState = useSelector(lessonState);
|
|
const currentLnId = useSelector(currentLessonId);
|
|
|
|
const [updateLessonState] = useUpdateLessonStateMutation();
|
|
|
|
console.log("lesson state", lnState);
|
|
|
|
useEffect(() => {
|
|
form.setFieldsValue({
|
|
state: lnState,
|
|
});
|
|
}, [lnState]);
|
|
|
|
return (
|
|
<Flex justify="space-between" vertical style={{ height: "100%" }}>
|
|
<MyContainer>
|
|
<Search
|
|
placeholder="What would you like to insert?"
|
|
style={{ paddingBottom: 16 }}
|
|
/>
|
|
|
|
<DndContext
|
|
onDragStart={(event) => {
|
|
console.log("drag start", event.active.id);
|
|
|
|
setIsDragging(event.active.id.toString());
|
|
}}
|
|
onDragEnd={(evnet) => {
|
|
console.log("drag end", evnet.active.id);
|
|
setIsDragging(null);
|
|
}}
|
|
>
|
|
{componentsGroups.map((group, i) => (
|
|
<div key={i}>
|
|
<span>{group.category}</span>
|
|
|
|
<Flex gap={16} wrap style={{ paddingTop: 16 }}>
|
|
{group.components.map((component, i) => (
|
|
<DraggableCreateComponent key={i} component={component} />
|
|
))}
|
|
</Flex>
|
|
</div>
|
|
))}
|
|
{createPortal(
|
|
<DragOverlay>
|
|
{isDragging
|
|
? (() => {
|
|
const comp = componentsGroups
|
|
.flatMap((group) => group.components)
|
|
.find((comp) => "draggable_" + comp.name === isDragging);
|
|
console.log("dragging", comp);
|
|
if (!comp) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div style={{}}>
|
|
<CreateComponent component={comp} />
|
|
</div>
|
|
);
|
|
})()
|
|
: null}
|
|
</DragOverlay>,
|
|
document.body
|
|
)}
|
|
</DndContext>
|
|
</MyContainer>
|
|
|
|
<div style={{ padding: 12 }}>
|
|
<Form form={form} layout="vertical">
|
|
<Form.Item label="Status" name="state" style={{ marginBottom: 0 }}>
|
|
<Select
|
|
style={{ width: "100%" }}
|
|
onChange={async (value) => {
|
|
console.log("state changed", value, lessonId);
|
|
|
|
try {
|
|
await updateLessonState({
|
|
lessonId: currentLnId,
|
|
newState: value,
|
|
}).unwrap();
|
|
} catch (err) {
|
|
console.log("error", err);
|
|
}
|
|
}}
|
|
>
|
|
<Select.Option value={LessonState.Published}>
|
|
Published
|
|
</Select.Option>
|
|
<Select.Option value={LessonState.Draft}>Draft</Select.Option>
|
|
</Select>
|
|
</Form.Item>
|
|
</Form>
|
|
</div>
|
|
</Flex>
|
|
);
|
|
}
|
|
|
|
export function DraggableCreateComponent({
|
|
component,
|
|
}: {
|
|
component: Component;
|
|
}) {
|
|
const dispatch = useDispatch();
|
|
|
|
/*const { attributes, listeners, setNodeRef, transform, isDragging, active } =
|
|
useDraggable({
|
|
id: "draggable_" + component.name,
|
|
});*/
|
|
|
|
return (
|
|
<>
|
|
<div
|
|
//ref={setNodeRef}
|
|
//{...attributes}
|
|
//{...listeners}
|
|
style={
|
|
{
|
|
//transition: !isDragging ? "all 0.3s ease-out" : "none",
|
|
//boxShadow: isDragging ? "0px 0px 10px 0px #00000040" : "none",
|
|
//opacity: isDragging ? 0.25 : 1,
|
|
}
|
|
}
|
|
>
|
|
<CreateComponent component={component} />
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function CreateComponent({ component }: { component: Component }) {
|
|
const dispatch = useDispatch();
|
|
const isDarkMode = useSelector(darkMode);
|
|
|
|
const currentLnId = useSelector(currentLessonId);
|
|
|
|
const [reqAddLessonContent] = useAddLessonContentMutation();
|
|
|
|
return (
|
|
<Flex
|
|
vertical
|
|
align="center"
|
|
justify="center"
|
|
style={{
|
|
backgroundColor: !isDarkMode ? "#f0f2f5" : "#1f1f1f",
|
|
height: 80,
|
|
width: 80,
|
|
cursor: "pointer",
|
|
}}
|
|
onClick={async () => {
|
|
console.log("insert component", component.type);
|
|
|
|
try {
|
|
const res = await reqAddLessonContent({
|
|
lessonId: currentLnId,
|
|
type: component.type,
|
|
data: component.defaultData || "",
|
|
}).unwrap();
|
|
|
|
console.log("add content", component);
|
|
|
|
dispatch(
|
|
addLessonContent({
|
|
Id: res.Id,
|
|
Type: component.type,
|
|
Data: component.defaultData || "",
|
|
Page: 1,
|
|
Position: 1,
|
|
})
|
|
);
|
|
} catch (err) {
|
|
console.log("error", err);
|
|
}
|
|
}}
|
|
>
|
|
{component.thumbnail ? (
|
|
<div>
|
|
<img
|
|
src={component.thumbnail}
|
|
style={{
|
|
width: 40,
|
|
filter:
|
|
isDarkMode && component.invertThumbnailAtDarkmode
|
|
? "invert(1)"
|
|
: "invert(0)",
|
|
}}
|
|
/>
|
|
</div>
|
|
) : null}
|
|
<Typography.Text style={{ fontSize: 12 }}>
|
|
{component.name}
|
|
</Typography.Text>
|
|
</Flex>
|
|
);
|
|
}
|
|
|
|
//console.log("insert component", component.type);
|
|
//dispatch(addLessonContent(component.type));
|