mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-10 02:54:15 +00:00
428 lines
9.4 KiB
JavaScript
Executable File
428 lines
9.4 KiB
JavaScript
Executable File
import React from "react"
|
|
import { Menu, Avatar, Button, Dropdown } 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/sidebar"
|
|
|
|
import "./index.less"
|
|
|
|
const onClickHandlers = {
|
|
settings: () => {
|
|
window.app.navigation.goToSettings()
|
|
},
|
|
notifications: () => {
|
|
window.app.controls.openNotifications()
|
|
},
|
|
search: () => {
|
|
window.app.controls.openSearcher()
|
|
},
|
|
create: () => {
|
|
window.app.controls.openCreator()
|
|
},
|
|
account: () => {
|
|
window.app.navigation.goToAccount()
|
|
},
|
|
login: () => {
|
|
window.app.navigation.goAuth()
|
|
}
|
|
}
|
|
|
|
const getSidebarComponents = () => {
|
|
const items = {}
|
|
|
|
sidebarItems.forEach((item, index) => {
|
|
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,
|
|
}
|
|
}
|
|
|
|
const CustomRender = (props) => {
|
|
const handleClickOutside = (event) => {
|
|
if (props.sidebarRef.current && !props.sidebarRef.current.contains(event.target)) {
|
|
if (event.target.closest(".ant-select-item")) {
|
|
return
|
|
}
|
|
|
|
props.closeRender()
|
|
}
|
|
}
|
|
|
|
React.useEffect(() => {
|
|
document.addEventListener("mousedown", handleClickOutside)
|
|
|
|
return () => {
|
|
document.removeEventListener("mousedown", handleClickOutside)
|
|
}
|
|
}, [])
|
|
|
|
return <div className="render_content_wrapper">
|
|
<div className="render_content_header">
|
|
{
|
|
props.customRenderTitle ?? null
|
|
}
|
|
<Button
|
|
onClick={props.closeRender}
|
|
>
|
|
Close
|
|
</Button>
|
|
</div>
|
|
<div className="render_content">
|
|
{props.children}
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
export default class Sidebar extends React.Component {
|
|
constructor(props) {
|
|
super(props)
|
|
|
|
this.controller = window.app["SidebarController"] = {
|
|
toggleVisibility: this.toggleVisibility,
|
|
toggleElevation: this.toggleElevation,
|
|
toggleCollapse: this.toggleExpanded,
|
|
isVisible: () => this.state.visible,
|
|
isExpanded: () => this.state.expanded,
|
|
setCustomRender: this.setRender,
|
|
closeCustomRender: this.closeRender,
|
|
updateBackgroundItem: (children, props) => {
|
|
let updatedValue = this.state.backgroundItem
|
|
|
|
if (typeof children !== "undefined") {
|
|
updatedValue.children = children
|
|
}
|
|
|
|
if (typeof props !== "undefined") {
|
|
updatedValue.props = props
|
|
}
|
|
|
|
this.setState({
|
|
backgroundItem: updatedValue
|
|
})
|
|
},
|
|
setBackgroundItem: (children, props) => {
|
|
this.setState({
|
|
backgroundItem: {
|
|
children: children,
|
|
props: props,
|
|
},
|
|
})
|
|
},
|
|
clearBackgroundItem: () => {
|
|
this.setState({
|
|
backgroundItem: null,
|
|
})
|
|
}
|
|
}
|
|
|
|
this.state = {
|
|
visible: false,
|
|
elevated: false,
|
|
expanded: false,
|
|
pathResolvers: null,
|
|
menus: null,
|
|
|
|
customRenderTitle: null,
|
|
customRender: null,
|
|
backgroundItem: null,
|
|
}
|
|
|
|
// handle sidedrawer open/close
|
|
window.app.eventBus.on("sidedrawer.hasDrawers", () => {
|
|
this.toggleElevation(true)
|
|
})
|
|
window.app.eventBus.on("sidedrawer.noDrawers", () => {
|
|
this.toggleElevation(false)
|
|
})
|
|
}
|
|
|
|
sidebarRef = React.createRef()
|
|
|
|
collapseDebounce = null
|
|
|
|
componentDidMount = async () => {
|
|
await this.loadItems()
|
|
|
|
setTimeout(() => {
|
|
this.controller.toggleVisibility(true)
|
|
}, 100)
|
|
}
|
|
|
|
setRender = async (render, options = {}) => {
|
|
if (!typeof render === "function") {
|
|
throw new Error("Render is required to be a function")
|
|
}
|
|
|
|
await 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.ignoreClick) {
|
|
return
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
window.app.cores.sound.useUIAudio("sidebar.switch_tab")
|
|
|
|
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)
|
|
}
|
|
|
|
toggleExpanded = (to) => {
|
|
this.setState({ expanded: to ?? !this.state.expanded })
|
|
}
|
|
|
|
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.cores.settings.is("collapseOnLooseFocus", false)) return
|
|
|
|
clearTimeout(this.collapseDebounce)
|
|
|
|
this.collapseDebounce = null
|
|
|
|
if (!this.state.expanded) {
|
|
this.toggleExpanded(true)
|
|
}
|
|
}
|
|
|
|
handleMouseLeave = () => {
|
|
if (!this.state.visible) return
|
|
|
|
if (window.app.cores.settings.is("collapseOnLooseFocus", false)) return
|
|
|
|
if (this.state.expanded) {
|
|
this.collapseDebounce = setTimeout(() => {
|
|
this.toggleExpanded(false)
|
|
}, window.app.cores.settings.get("autoCollapseDelay") ?? 500)
|
|
}
|
|
}
|
|
|
|
render() {
|
|
if (!this.state.menus) return null
|
|
|
|
const defaultSelectedKey = window.location.pathname.replace("/", "")
|
|
|
|
return <div
|
|
onMouseEnter={this.onMouseEnter}
|
|
onMouseLeave={this.handleMouseLeave}
|
|
className={
|
|
classnames(
|
|
"app_sidebar",
|
|
{
|
|
["customRender"]: this.state.customRender,
|
|
["floating"]: window.app?.cores.settings.get("sidebar.floating"),
|
|
["elevated"]: this.state.visible && this.state.elevated,
|
|
["expanded"]: this.state.visible && this.state.expanded,
|
|
["hidden"]: !this.state.visible,
|
|
}
|
|
)
|
|
}
|
|
ref={this.sidebarRef}
|
|
>
|
|
{
|
|
this.state.customRender && <CustomRender
|
|
customRenderTitle={this.state.customRenderTitle}
|
|
closeRender={this.closeRender}
|
|
sidebarRef={this.sidebarRef}
|
|
>
|
|
{this.state.customRender}
|
|
</CustomRender>
|
|
}
|
|
|
|
{
|
|
!this.state.customRender && <>
|
|
<div className="app_sidebar_header">
|
|
<div className="app_sidebar_header_logo">
|
|
<img src={config.logo?.alt} />
|
|
</div>
|
|
</div>
|
|
|
|
<div key="menu" className="app_sidebar_menu_wrapper">
|
|
<Menu
|
|
selectable={true}
|
|
mode="inline"
|
|
onClick={this.handleClick}
|
|
defaultSelectedKeys={[defaultSelectedKey]}
|
|
>
|
|
{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
|
|
{...this.state.backgroundItem?.props ?? {}}
|
|
key="background_item"
|
|
className={classnames("background_item", { ["active"]: this.state.backgroundItem })}
|
|
ignoreClick
|
|
>
|
|
{
|
|
this.state.backgroundItem?.children
|
|
}
|
|
</Menu.Item>
|
|
<Menu.Item key="search" icon={<Icons.Search />} >
|
|
<Translation>
|
|
{(t) => t("Search")}
|
|
</Translation>
|
|
</Menu.Item>
|
|
<Menu.Item key="notifications" icon={<Icons.Bell />}>
|
|
<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"
|
|
onClick={() => {
|
|
window.app.navigation.goToAccount()
|
|
}}
|
|
>
|
|
<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>
|
|
}
|
|
} |