mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-10 02:54:15 +00:00
337 lines
7.8 KiB
JavaScript
Executable File
337 lines
7.8 KiB
JavaScript
Executable File
import React from "react"
|
|
import { Menu, Avatar, Button } from "antd"
|
|
import { Translation } from "react-i18next"
|
|
import classnames from "classnames"
|
|
|
|
import config from "config"
|
|
import { Icons, createIconRender } from "components/Icons"
|
|
|
|
import sidebarItems from "schemas/routes.json"
|
|
|
|
import "./index.less"
|
|
|
|
const onClickHandlers = {
|
|
settings: (event) => {
|
|
window.app.openSettings()
|
|
},
|
|
}
|
|
|
|
const getSidebarComponents = () => {
|
|
const items = {}
|
|
|
|
sidebarItems.forEach((item, index) => {
|
|
if (!item.reachable) {
|
|
return
|
|
}
|
|
|
|
items[item.id] = {
|
|
...item,
|
|
index,
|
|
content: (
|
|
<>
|
|
{createIconRender(item.icon)} {item.title}
|
|
</>
|
|
),
|
|
}
|
|
})
|
|
|
|
return items
|
|
}
|
|
|
|
const generateItems = () => {
|
|
const components = getSidebarComponents()
|
|
|
|
const itemsMap = []
|
|
const pathResolvers = {}
|
|
|
|
Object.keys(components).forEach((key, index) => {
|
|
const component = components[key]
|
|
|
|
if (typeof component.path !== "undefined") {
|
|
pathResolvers[component.id] = component.path
|
|
}
|
|
|
|
itemsMap.push(component)
|
|
})
|
|
|
|
return {
|
|
itemsMap,
|
|
pathResolvers,
|
|
}
|
|
}
|
|
|
|
export default class Sidebar extends React.Component {
|
|
constructor(props) {
|
|
super(props)
|
|
|
|
this.controller = window.app["SidebarController"] = {
|
|
toggleVisibility: this.toggleVisibility,
|
|
toggleElevation: this.toggleElevation,
|
|
toggleCollapse: this.toggleCollapse,
|
|
isVisible: () => this.state.visible,
|
|
isCollapsed: () => this.state.collapsed,
|
|
setCustomRender: this.setRender,
|
|
closeCustomRender: this.closeRender,
|
|
}
|
|
|
|
this.state = {
|
|
visible: false,
|
|
elevated: false,
|
|
collapsed: window.app.settings.get("collapseOnLooseFocus") ?? false,
|
|
pathResolvers: null,
|
|
menus: null,
|
|
|
|
customRenderTitle: null,
|
|
customRender: null,
|
|
}
|
|
|
|
// handle sidedrawer open/close
|
|
window.app.eventBus.on("sidedrawer.hasDrawers", () => {
|
|
this.toggleElevation(true)
|
|
})
|
|
window.app.eventBus.on("sidedrawer.noDrawers", () => {
|
|
this.toggleElevation(false)
|
|
})
|
|
}
|
|
|
|
collapseDebounce = null
|
|
|
|
componentDidMount = async () => {
|
|
await this.loadItems()
|
|
|
|
setTimeout(() => {
|
|
this.controller.toggleVisibility(true)
|
|
}, 100)
|
|
}
|
|
|
|
setRender = (render, options = {}) => {
|
|
if (!typeof render === "function") {
|
|
throw new Error("Render is required to be a function")
|
|
}
|
|
|
|
this.setState({
|
|
customRenderTitle: <div className="render_content_header_title">
|
|
{
|
|
options.icon && createIconRender(options.icon)
|
|
}
|
|
{
|
|
options.title && <h1>
|
|
<Translation>
|
|
{t => t(options.title)}
|
|
</Translation>
|
|
</h1>
|
|
}
|
|
</div>,
|
|
customRender: React.createElement(render, {
|
|
...options.props ?? {},
|
|
close: this.closeRender,
|
|
})
|
|
})
|
|
}
|
|
|
|
closeRender = () => {
|
|
this.setState({
|
|
customRenderTitle: null,
|
|
customRender: null,
|
|
})
|
|
}
|
|
|
|
loadItems = async () => {
|
|
const generation = generateItems()
|
|
|
|
// update states
|
|
await this.setState({
|
|
menus: generation.itemsMap,
|
|
pathResolvers: generation.pathResolvers,
|
|
})
|
|
}
|
|
|
|
renderMenuItems(items) {
|
|
const handleRenderIcon = (icon) => {
|
|
if (typeof icon === "undefined") {
|
|
return null
|
|
}
|
|
return createIconRender(icon)
|
|
}
|
|
|
|
return items.map((item) => {
|
|
if (Array.isArray(item.children)) {
|
|
return <Menu.SubMenu
|
|
key={item.id}
|
|
icon={handleRenderIcon(item.icon)}
|
|
title={<span>
|
|
<Translation>
|
|
{t => t(item.title)}
|
|
</Translation>
|
|
</span>}
|
|
{...item.props}
|
|
>
|
|
{this.renderMenuItems(item.children)}
|
|
</Menu.SubMenu>
|
|
}
|
|
|
|
return <Menu.Item key={item.id} icon={handleRenderIcon(item.icon)} {...item.props}>
|
|
<Translation>
|
|
{t => t(item.title ?? item.id)}
|
|
</Translation>
|
|
</Menu.Item>
|
|
})
|
|
}
|
|
|
|
handleClick = (e) => {
|
|
if (e.item.props.override_event) {
|
|
return app.eventBus.emit(e.item.props.override_event, e.item.props.override_event_props)
|
|
}
|
|
|
|
if (typeof e.key === "undefined") {
|
|
window.app.eventBus.emit("invalidSidebarKey", e)
|
|
return false
|
|
}
|
|
|
|
if (typeof onClickHandlers[e.key] === "function") {
|
|
return onClickHandlers[e.key](e)
|
|
}
|
|
|
|
if (typeof this.state.pathResolvers === "object") {
|
|
if (typeof this.state.pathResolvers[e.key] !== "undefined") {
|
|
return window.app.setLocation(`/${this.state.pathResolvers[e.key]}`, 150)
|
|
}
|
|
}
|
|
|
|
return window.app.setLocation(`/${e.key}`, 150)
|
|
}
|
|
|
|
toggleCollapse = (to) => {
|
|
if (!this.state.editMode) {
|
|
this.setState({ collapsed: to ?? !this.state.collapsed })
|
|
}
|
|
}
|
|
|
|
toggleVisibility = (to) => {
|
|
this.setState({ visible: to ?? !this.state.visible })
|
|
}
|
|
|
|
toggleElevation = (to) => {
|
|
this.setState({ elevated: to ?? !this.state.elevated })
|
|
}
|
|
|
|
onMouseEnter = () => {
|
|
if (!this.state.visible) return
|
|
|
|
if (window.app.settings.is("collapseOnLooseFocus", false)) return
|
|
|
|
clearTimeout(this.collapseDebounce)
|
|
|
|
this.collapseDebounce = null
|
|
|
|
if (this.state.collapsed) {
|
|
this.toggleCollapse(false)
|
|
}
|
|
}
|
|
|
|
handleMouseLeave = () => {
|
|
if (!this.state.visible) return
|
|
|
|
if (window.app.settings.is("collapseOnLooseFocus", false)) return
|
|
|
|
if (!this.state.collapsed) {
|
|
this.collapseDebounce = setTimeout(() => { this.toggleCollapse(true) }, window.app.settings.get("autoCollapseDelay") ?? 500)
|
|
}
|
|
}
|
|
|
|
render() {
|
|
if (!this.state.menus) return null
|
|
|
|
return (
|
|
<div
|
|
onMouseEnter={this.onMouseEnter}
|
|
onMouseLeave={this.handleMouseLeave}
|
|
className={
|
|
classnames(
|
|
"app_sidebar",
|
|
{
|
|
["customRender"]: this.state.customRender,
|
|
["floating"]: window.app?.settings.get("sidebar.floating"),
|
|
["collapsed"]: !this.state.customRender && this.state.visible && this.state.collapsed,
|
|
["elevated"]: !this.state.visible && this.state.elevated,
|
|
["hidden"]: !this.state.visible,
|
|
}
|
|
)
|
|
}
|
|
>
|
|
|
|
{
|
|
this.state.customRender &&
|
|
<div className="render_content_wrapper">
|
|
<div className="render_content_header">
|
|
{
|
|
this.state.customRenderTitle ?? null
|
|
}
|
|
<Button
|
|
onClick={this.closeRender}
|
|
>
|
|
Close
|
|
</Button>
|
|
</div>
|
|
<div className="render_content">
|
|
{this.state.customRender}
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
{
|
|
!this.state.customRender && <>
|
|
<div className="app_sidebar_header">
|
|
<div className={classnames("app_sidebar_header_logo", { ["collapsed"]: this.state.collapsed })}>
|
|
<img src={this.state.collapsed ? config.logo?.alt : config.logo?.full} />
|
|
</div>
|
|
</div>
|
|
|
|
<div key="menu" className="app_sidebar_menu_wrapper">
|
|
<Menu selectable={true} mode="inline" onClick={this.handleClick}>
|
|
{this.renderMenuItems(this.state.menus)}
|
|
</Menu>
|
|
</div>
|
|
|
|
<div key="bottom" className={classnames("app_sidebar_menu_wrapper", "bottom")}>
|
|
<Menu selectable={false} mode="inline" onClick={this.handleClick}>
|
|
<Menu.Item key="search" icon={<Icons.Search />} override_event="app.openSearcher" >
|
|
<Translation>
|
|
{(t) => t("Search")}
|
|
</Translation>
|
|
</Menu.Item>
|
|
<Menu.Item key="create" icon={<Icons.PlusCircle />} override_event="app.openCreator" >
|
|
<Translation>
|
|
{(t) => t("Create")}
|
|
</Translation>
|
|
</Menu.Item>
|
|
<Menu.Item key="notifications" icon={<Icons.Bell />} override_event="app.openNotifications">
|
|
<Translation>
|
|
{t => t("Notifications")}
|
|
</Translation>
|
|
</Menu.Item>
|
|
<Menu.Item key="settings" icon={<Icons.Settings />}>
|
|
<Translation>
|
|
{t => t("Settings")}
|
|
</Translation>
|
|
</Menu.Item>
|
|
{
|
|
app.userData && <Menu.Item key="account" className="user_avatar">
|
|
<Avatar shape="square" src={app.userData?.avatar} />
|
|
</Menu.Item>
|
|
}
|
|
{
|
|
!app.userData && <Menu.Item key="login" icon={<Icons.LogIn />}>
|
|
<Translation>
|
|
{t => t("Login")}
|
|
</Translation>
|
|
</Menu.Item>
|
|
}
|
|
</Menu>
|
|
</div>
|
|
</>
|
|
}
|
|
</div>
|
|
)
|
|
}
|
|
} |