mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-10 02:54:15 +00:00
396 lines
9.2 KiB
JavaScript
396 lines
9.2 KiB
JavaScript
import React from "react"
|
|
import { Layout, Menu, Avatar } from "antd"
|
|
import classnames from "classnames"
|
|
|
|
import config from "config"
|
|
import { Icons, createIconRender } from "components/Icons"
|
|
import { sidebarKeys as defaultSidebarItems } from "schemas/defaultSettings"
|
|
import sidebarItems from "schemas/routes.json"
|
|
import { Translation } from "react-i18next"
|
|
|
|
import { SidebarEditor } from "./components"
|
|
import "./index.less"
|
|
|
|
const { Sider } = Layout
|
|
|
|
const onClickHandlers = {
|
|
settings: (event) => {
|
|
window.app.openSettings()
|
|
},
|
|
}
|
|
|
|
export default class Sidebar extends React.Component {
|
|
constructor(props) {
|
|
super(props)
|
|
|
|
this.controller = window.app["SidebarController"] = {
|
|
toggleVisibility: this.toggleVisibility,
|
|
toggleEdit: this.toggleEditMode,
|
|
toggleElevation: this.toggleElevation,
|
|
attachElement: this.attachElement,
|
|
isVisible: () => this.state.visible,
|
|
isEditMode: () => this.state.visible,
|
|
isCollapsed: () => this.state.collapsed,
|
|
}
|
|
|
|
this.state = {
|
|
editMode: false,
|
|
visible: false,
|
|
loading: true,
|
|
collapsed: window.app.settings.get("collapseOnLooseFocus") ?? false,
|
|
pathResolve: {},
|
|
menus: {},
|
|
extraItems: {
|
|
bottom: [],
|
|
top: [],
|
|
},
|
|
elevated: false,
|
|
additionalElements: [],
|
|
}
|
|
|
|
window.app.eventBus.on("edit_sidebar", () => this.toggleEditMode())
|
|
|
|
window.app.eventBus.on("settingChanged.sidebar_collapse", (value) => {
|
|
this.toggleCollapse(value)
|
|
})
|
|
|
|
// 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.loadSidebarItems()
|
|
|
|
setTimeout(() => {
|
|
this.controller.toggleVisibility(true)
|
|
}, 100)
|
|
}
|
|
|
|
getStoragedKeys = () => {
|
|
return window.app.settings.get("sidebarKeys")
|
|
}
|
|
|
|
attachElement = (element) => {
|
|
this.setState({
|
|
additionalElements: [...this.state.additionalElements, element],
|
|
})
|
|
}
|
|
|
|
appendItem = (item = {}) => {
|
|
const { position } = item
|
|
|
|
if (typeof position === "undefined" && typeof this.state.extraItems[position] === "undefined") {
|
|
console.error("Invalid position")
|
|
return false
|
|
}
|
|
|
|
const state = this.state.extraItems
|
|
|
|
state[position].push(item)
|
|
|
|
this.setState({ extraItems: state })
|
|
}
|
|
|
|
loadSidebarItems = () => {
|
|
const items = {}
|
|
const itemsMap = []
|
|
|
|
// parse all items from schema
|
|
sidebarItems.forEach((item, index) => {
|
|
items[item.id] = {
|
|
...item,
|
|
index,
|
|
content: (
|
|
<>
|
|
{createIconRender(item.icon)} {item.title}
|
|
</>
|
|
),
|
|
}
|
|
})
|
|
|
|
// filter undefined to avoid error
|
|
let keys = (this.getStoragedKeys() ?? defaultSidebarItems).filter((key) => {
|
|
if (typeof items[key] !== "undefined") {
|
|
return true
|
|
}
|
|
})
|
|
|
|
// short items
|
|
keys.forEach((id, index) => {
|
|
const item = items[id]
|
|
|
|
if (item.locked) {
|
|
if (item.index !== index) {
|
|
keys = keys.move(index, item.index)
|
|
|
|
//update index
|
|
window.app.settings.set("sidebarKeys", keys)
|
|
}
|
|
}
|
|
})
|
|
|
|
// set items from scoped keys
|
|
keys.forEach((key, index) => {
|
|
const item = items[key]
|
|
|
|
try {
|
|
// avoid if item is duplicated
|
|
if (itemsMap.includes(item)) {
|
|
return false
|
|
}
|
|
|
|
let valid = true
|
|
|
|
if (typeof item.requireState === "object") {
|
|
const { key, value } = item.requireState
|
|
//* TODO: check global state
|
|
}
|
|
|
|
// end validation
|
|
if (!valid) {
|
|
return false
|
|
}
|
|
|
|
if (typeof item.path !== "undefined") {
|
|
let resolvers = this.state.pathResolve ?? {}
|
|
resolvers[item.id] = item.path
|
|
this.setState({ pathResolve: resolvers })
|
|
}
|
|
|
|
itemsMap.push(item)
|
|
} catch (error) {
|
|
return console.log(error)
|
|
}
|
|
})
|
|
|
|
// update states
|
|
this.setState({ items, menus: itemsMap, loading: false })
|
|
}
|
|
|
|
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.overrideEvent) {
|
|
return app.eventBus.emit(e.item.props.overrideEvent, e.item.props.overrideEventProps)
|
|
}
|
|
|
|
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.pathResolve[e.key] !== "undefined") {
|
|
return window.app.setLocation(`/${this.state.pathResolve[e.key]}`, 150)
|
|
}
|
|
|
|
return window.app.setLocation(`/${e.key}`, 150)
|
|
}
|
|
|
|
toggleEditMode = (to) => {
|
|
if (typeof to === "undefined") {
|
|
to = !this.state.editMode
|
|
}
|
|
|
|
if (to) {
|
|
window.app.eventBus.emit("clearAllOverlays")
|
|
} else {
|
|
if (this.itemsMap !== this.getStoragedKeys()) {
|
|
this.loadSidebarItems()
|
|
}
|
|
}
|
|
|
|
this.setState({ editMode: to, collapsed: false })
|
|
}
|
|
|
|
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 (window.app.settings.is("collapseOnLooseFocus", false)) {
|
|
return false
|
|
}
|
|
|
|
clearTimeout(this.collapseDebounce)
|
|
this.collapseDebounce = null
|
|
|
|
if (this.state.collapsed) {
|
|
this.toggleCollapse(false)
|
|
}
|
|
}
|
|
|
|
handleMouseLeave = () => {
|
|
if (window.app.settings.is("collapseOnLooseFocus", false)) {
|
|
return false
|
|
}
|
|
|
|
if (!this.state.collapsed) {
|
|
this.collapseDebounce = setTimeout(() => { this.toggleCollapse(true) }, window.app.settings.get("autoCollapseDelay") ?? 500)
|
|
}
|
|
}
|
|
|
|
renderExtraItems = (position) => {
|
|
return this.state.extraItems[position].map((item = {}) => {
|
|
if (typeof item.icon !== "undefined") {
|
|
if (typeof item.props !== "object") {
|
|
item.props = Object()
|
|
}
|
|
|
|
item.props["icon"] = createIconRender(item.icon)
|
|
}
|
|
|
|
return <Menu.Item key={item.id} {...item.props}>{item.children}</Menu.Item>
|
|
})
|
|
}
|
|
|
|
render() {
|
|
if (this.state.loading) return null
|
|
|
|
const { user } = this.props
|
|
|
|
return (
|
|
<Sider
|
|
onMouseEnter={this.onMouseEnter}
|
|
onMouseLeave={this.handleMouseLeave}
|
|
theme={this.props.theme}
|
|
width={this.state.editMode ? 400 : 200}
|
|
collapsed={this.state.editMode ? false : this.state.collapsed}
|
|
onCollapse={() => this.props.onCollapse()}
|
|
className={
|
|
classnames(
|
|
"sidebar",
|
|
{
|
|
["edit_mode"]: this.state.editMode,
|
|
["hidden"]: !this.state.visible,
|
|
["elevated"]: this.state.elevated
|
|
}
|
|
)
|
|
}
|
|
>
|
|
<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>
|
|
|
|
{this.state.editMode && (
|
|
<div style={{ height: "100%" }}>
|
|
<SidebarEditor />
|
|
</div>
|
|
)}
|
|
|
|
{!this.state.editMode && (
|
|
<div key="menu" className="app_sidebar_menu">
|
|
<Menu selectable={true} mode="inline" theme={this.props.theme} onClick={this.handleClick}>
|
|
{this.renderMenuItems(this.state.menus)}
|
|
{this.renderExtraItems("top")}
|
|
</Menu>
|
|
</div>
|
|
)}
|
|
|
|
{!this.state.editMode && <div key="additionalElements" className="additionalElements">
|
|
{this.state.additionalElements}
|
|
</div>}
|
|
|
|
{!this.state.editMode && (
|
|
<div key="bottom" className="app_sidebar_bottom">
|
|
<Menu selectable={false} mode="inline" theme={this.props.theme} onClick={this.handleClick}>
|
|
<Menu.Item key="search" icon={<Icons.Search />} overrideEvent="app.openSearch" >
|
|
<Translation>
|
|
{(t) => t("Search")}
|
|
</Translation>
|
|
</Menu.Item>
|
|
<Menu.Item key="create" icon={<Icons.PlusCircle />} overrideEvent="app.openCreator" >
|
|
<Translation>
|
|
{(t) => t("Create")}
|
|
</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>
|
|
|
|
{
|
|
user && <Menu.Item key="account">
|
|
<div className="user_avatar">
|
|
<Avatar shape="square" src={user?.avatar} />
|
|
</div>
|
|
</Menu.Item>
|
|
}
|
|
|
|
{
|
|
!user && <Menu.Item key="login" icon={<Icons.LogIn />}>
|
|
<Translation>
|
|
{t => t("Login")}
|
|
</Translation>
|
|
</Menu.Item>
|
|
}
|
|
|
|
{this.renderExtraItems("bottom")}
|
|
</Menu>
|
|
</div>
|
|
)}
|
|
</Sider>
|
|
)
|
|
}
|
|
} |