mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 18:44:16 +00:00
refactor & restyle sidebar
This commit is contained in:
parent
8787dd16f4
commit
e28efd1698
@ -6,14 +6,12 @@
|
||||
"notifications_vibrate": true,
|
||||
"notifications_sound_volume": 50,
|
||||
"longPressDelay": 600,
|
||||
"autoCollapseDelay": 500,
|
||||
"autoCollapseDelayEnabled": true,
|
||||
"sidebar.collapsable": true,
|
||||
"sidebar.collapse_delay_time": 500,
|
||||
"haptic_feedback": true,
|
||||
"collapseOnLooseFocus": true,
|
||||
"style.auto_darkMode": true,
|
||||
"feed_max_fetch": 20,
|
||||
"style.compactMode": false,
|
||||
"sidebar.floating": false,
|
||||
"player.compressor": false,
|
||||
"language": "en"
|
||||
}
|
@ -12,17 +12,6 @@ export default {
|
||||
group: "app",
|
||||
order: 1,
|
||||
settings: [
|
||||
{
|
||||
id: "sidebar.floating",
|
||||
title: "Floating Sidebar",
|
||||
description: "Make the sidebar float over layout content.",
|
||||
component: "Switch",
|
||||
icon: "MdOutlineLastPage",
|
||||
group: "layout",
|
||||
emitEvent: "app.softReload",
|
||||
storaged: true,
|
||||
mobile: false,
|
||||
},
|
||||
{
|
||||
id: "style.reduceAnimations",
|
||||
group: "animations",
|
||||
|
@ -148,24 +148,23 @@ export default {
|
||||
mobile: false,
|
||||
},
|
||||
{
|
||||
id: "collapseOnLooseFocus",
|
||||
storaged: true,
|
||||
id: "sidebar.collapsable",
|
||||
group: "sidebar",
|
||||
component: "Switch",
|
||||
icon: "Columns",
|
||||
title: "Auto Collapse",
|
||||
description: "Collapse the sidebar when loose focus",
|
||||
description: "Allow to collapse the sidebar when loose focus.",
|
||||
emitEvent: "settingChanged.sidebar_collapse",
|
||||
storaged: true,
|
||||
mobile: false,
|
||||
},
|
||||
{
|
||||
id: "autoCollapseDelay",
|
||||
storaged: true,
|
||||
id: "sidebar.collapse_delay_time",
|
||||
group: "sidebar",
|
||||
component: "Slider",
|
||||
icon: "MdTimer",
|
||||
dependsOn: {
|
||||
"collapseOnLooseFocus": true
|
||||
"sidebar.collapsable": true
|
||||
},
|
||||
title: "Auto Collapse timeout",
|
||||
description: "Set the delay before the sidebar is collapsed",
|
||||
@ -181,6 +180,7 @@ export default {
|
||||
2000: "2s",
|
||||
}
|
||||
},
|
||||
storaged: true,
|
||||
mobile: false,
|
||||
},
|
||||
{
|
||||
|
@ -1,267 +0,0 @@
|
||||
import React from "react"
|
||||
import { Button } from "antd"
|
||||
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"
|
||||
|
||||
import { ActionsBar } from "components"
|
||||
import { Icons, createIconRender } from "components/Icons"
|
||||
|
||||
import sidebarItems from "schemas/sidebar"
|
||||
import { sidebarKeys as defaultSidebarKeys } from "schemas/defaultSettings"
|
||||
|
||||
import Selector from "../selector"
|
||||
import "./index.less"
|
||||
|
||||
const allItemsMap = [...sidebarItems].map((item, index) => {
|
||||
item.key = index.toString()
|
||||
item.index = index
|
||||
return item
|
||||
})
|
||||
|
||||
const getAllItems = () => {
|
||||
let items = {}
|
||||
|
||||
allItemsMap.forEach((item) => {
|
||||
items[item.id] = {
|
||||
...item,
|
||||
content: (
|
||||
<>
|
||||
{createIconRender(item.icon)} {item.title}
|
||||
</>
|
||||
),
|
||||
}
|
||||
})
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
const allItems = getAllItems()
|
||||
|
||||
export default class SidebarEditor extends React.Component {
|
||||
state = {
|
||||
items: [],
|
||||
lockedIndex: [],
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadItems()
|
||||
}
|
||||
|
||||
loadItems = () => {
|
||||
const storagedKeys = window.app.cores.settings.get("sidebarKeys") ?? defaultSidebarKeys
|
||||
const active = []
|
||||
const lockedIndex = []
|
||||
|
||||
// set current active items
|
||||
storagedKeys.forEach((key) => {
|
||||
if (typeof allItems[key] !== "undefined") {
|
||||
if (allItems[key].locked) {
|
||||
lockedIndex.push(allItems[key].index)
|
||||
}
|
||||
active.push(key)
|
||||
}
|
||||
})
|
||||
|
||||
this.setState({ items: active, lockedIndex })
|
||||
}
|
||||
|
||||
onSave = () => {
|
||||
window.app.cores.settings.set("sidebarKeys", this.state.items)
|
||||
window.app.SidebarController.toggleEdit(false)
|
||||
}
|
||||
|
||||
onDiscard = () => {
|
||||
window.app.SidebarController.toggleEdit(false)
|
||||
}
|
||||
|
||||
onSetDefaults = () => {
|
||||
window.app.cores.settings.set("sidebarKeys", defaultSidebarKeys)
|
||||
this.loadItems()
|
||||
}
|
||||
|
||||
reorder = (list, startIndex, endIndex) => {
|
||||
const result = Array.from(list)
|
||||
const [removed] = result.splice(startIndex, 1)
|
||||
result.splice(endIndex, 0, removed)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
onDragEnd = (result) => {
|
||||
if (!result.destination) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.state.lockedIndex.includes(result.destination.index)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (allItems[result.draggableId].locked) {
|
||||
console.warn("Cannot move an locked item")
|
||||
return false
|
||||
}
|
||||
|
||||
const items = this.reorder(this.state.items, result.source.index, result.destination.index)
|
||||
|
||||
this.setState({ items })
|
||||
}
|
||||
|
||||
deleteItem = (key) => {
|
||||
// check if item is locked
|
||||
if (allItems[key].locked) {
|
||||
console.warn("Cannot delete an locked item")
|
||||
return false
|
||||
}
|
||||
|
||||
this.setState({ items: this.state.items.filter((item) => item !== key) })
|
||||
}
|
||||
|
||||
addItem = () => {
|
||||
const keys = []
|
||||
|
||||
// filter by active keys
|
||||
allItemsMap.forEach((item) => {
|
||||
if (!this.state.items.includes(item.id)) {
|
||||
keys.push(item.id)
|
||||
}
|
||||
})
|
||||
|
||||
window.app.DrawerController.open("sidebar_item_selector", Selector, {
|
||||
props: {
|
||||
width: "65%",
|
||||
},
|
||||
componentProps: {
|
||||
items: keys
|
||||
},
|
||||
onDone: (drawer, selectedKeys) => {
|
||||
drawer.close()
|
||||
|
||||
if (Array.isArray(selectedKeys)) {
|
||||
const update = this.state.items ?? []
|
||||
|
||||
selectedKeys.forEach((key) => {
|
||||
if (update.includes(key)) {
|
||||
return false
|
||||
}
|
||||
|
||||
update.push(key)
|
||||
})
|
||||
|
||||
this.setState({ items: update })
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const grid = 6
|
||||
|
||||
const getItemStyle = (isDragging, draggableStyle, component, isDraggingOver) => ({
|
||||
cursor: component.locked ? "not-allowed" : "grab",
|
||||
userSelect: "none",
|
||||
padding: grid * 2,
|
||||
margin: `0 0 ${grid}px 0`,
|
||||
borderRadius: "6px",
|
||||
transition: "150ms all ease-in-out",
|
||||
width: "100%",
|
||||
|
||||
border: isDraggingOver ? "2px dashed #e0e0e0" : "none",
|
||||
|
||||
color: component.locked ? "rgba(145,145,145,0.6)" : "#000",
|
||||
background: component.locked
|
||||
? "rgba(145, 145, 145, 0.2)"
|
||||
: isDragging
|
||||
? "rgba(145, 145, 145, 0.5)"
|
||||
: "transparent",
|
||||
...draggableStyle,
|
||||
})
|
||||
|
||||
const getListStyle = (isDraggingOver) => ({
|
||||
background: "transparent",
|
||||
transition: "150ms all ease-in-out",
|
||||
|
||||
padding: grid,
|
||||
width: "100%",
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DragDropContext onDragEnd={this.onDragEnd}>
|
||||
<Droppable droppableId="droppable">
|
||||
{(droppableProvided, droppableSnapshot) => (
|
||||
<div
|
||||
ref={droppableProvided.innerRef}
|
||||
style={getListStyle(droppableSnapshot.isDraggingOver)}
|
||||
>
|
||||
{this.state.items.map((key, index) => {
|
||||
const itemComponent = allItems[key]
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
isDragDisabled={itemComponent.locked}
|
||||
key={key}
|
||||
draggableId={key}
|
||||
index={index}
|
||||
>
|
||||
{(draggableProvided, draggableSnapshot) => (
|
||||
<div
|
||||
ref={draggableProvided.innerRef}
|
||||
{...draggableProvided.draggableProps}
|
||||
{...draggableProvided.dragHandleProps}
|
||||
style={getItemStyle(
|
||||
draggableSnapshot.isDragging,
|
||||
draggableProvided.draggableProps.style,
|
||||
itemComponent,
|
||||
droppableSnapshot.isDraggingOver,
|
||||
)}
|
||||
>
|
||||
{!allItems[key].locked &&
|
||||
<Icons.Trash
|
||||
onClick={() => this.deleteItem(key)}
|
||||
className="sidebar_editor_deleteBtn"
|
||||
/>
|
||||
}
|
||||
{itemComponent.icon && createIconRender(itemComponent.icon)}
|
||||
{itemComponent.title ?? itemComponent.id}
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
)
|
||||
})}
|
||||
{droppableProvided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
|
||||
<ActionsBar
|
||||
style={{ position: "absolute", bottom: 0, left: 0, width: "100%", borderRadius: "12px 12px 0 0" }}
|
||||
>
|
||||
<div>
|
||||
<Button
|
||||
style={{ lineHeight: 0 }}
|
||||
icon={<Icons.Plus style={{ margin: 0, padding: 0 }} />}
|
||||
onClick={this.addItem}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
style={{ lineHeight: 0 }}
|
||||
icon={<Icons.Check />}
|
||||
type="primary"
|
||||
onClick={this.onSave}
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button onClick={this.onDiscard} icon={<Icons.XCircle />} >Cancel</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button type="link" onClick={this.onSetDefaults}>Set defaults</Button>
|
||||
</div>
|
||||
</ActionsBar>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
.app_sidebar_sider_edit .ant-layout-sider-children{
|
||||
margin-top: 15px!important;
|
||||
|
||||
.app_sidebar_menu_wrapper {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar_editor_deleteBtn:hover{
|
||||
color: red;
|
||||
cursor: pointer;
|
||||
}
|
@ -1 +0,0 @@
|
||||
export { default as SidebarEditor } from './editor';
|
@ -1,76 +0,0 @@
|
||||
import React from "react"
|
||||
import { Icons, createIconRender } from "components/Icons"
|
||||
import { SelectableList } from "components"
|
||||
import { List } from "antd"
|
||||
|
||||
import sidebarItems from "schemas/sidebar"
|
||||
|
||||
import "./index.less"
|
||||
|
||||
const getStoragedKeys = () => {
|
||||
return window.app.cores.settings.get("sidebarKeys") ?? []
|
||||
}
|
||||
|
||||
const getAllItems = () => {
|
||||
const obj = {}
|
||||
|
||||
sidebarItems.forEach((item) => {
|
||||
obj[item.id] = item
|
||||
})
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
const allItems = getAllItems()
|
||||
|
||||
export default class SidebarItemSelector extends React.Component {
|
||||
state = {
|
||||
items: null,
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
const source = (this.props.items ?? getStoragedKeys() ?? []).map((key) => {
|
||||
return { key }
|
||||
})
|
||||
|
||||
this.setState({ items: source })
|
||||
}
|
||||
|
||||
handleDone = (selectedKeys) => {
|
||||
if (typeof this.props.onDone === "function") {
|
||||
this.props.onDone(selectedKeys)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>
|
||||
<Icons.PlusCircle /> Select items to add
|
||||
</h1>
|
||||
{this.state.items && (
|
||||
<SelectableList
|
||||
itemLayout="vertical"
|
||||
size="large"
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
}}
|
||||
onDone={this.handleDone}
|
||||
items={this.state.items ?? []}
|
||||
itemClassName="sidebar_selector_item"
|
||||
renderItem={(i) => {
|
||||
const item = allItems[i.key]
|
||||
|
||||
return (
|
||||
<List.Item key={item.title} className="sidebar_selector_item">
|
||||
{createIconRender(item.icon)}
|
||||
{item.title ?? item.id}
|
||||
</List.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
.sidebar_selector_item{
|
||||
height: fit-content;
|
||||
padding: 0;
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
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 classnames from "classnames"
|
||||
import { Translation } from "react-i18next"
|
||||
import { Motion, spring } from "react-motion"
|
||||
import { Menu, Avatar, Dropdown } from "antd"
|
||||
|
||||
import { Icons, createIconRender } from "components/Icons"
|
||||
|
||||
import sidebarItems from "schemas/sidebar"
|
||||
@ -34,259 +35,136 @@ const onClickHandlers = {
|
||||
}
|
||||
}
|
||||
|
||||
const getSidebarComponents = () => {
|
||||
const items = {}
|
||||
|
||||
sidebarItems.forEach((item, index) => {
|
||||
items[item.id] = {
|
||||
...item,
|
||||
index,
|
||||
content: (
|
||||
<>
|
||||
{createIconRender(item.icon)} {item.title}
|
||||
</>
|
||||
),
|
||||
const generateTopItems = () => {
|
||||
return sidebarItems.map((item) => {
|
||||
return {
|
||||
key: item.id,
|
||||
icon: createIconRender(item.icon),
|
||||
label: <Translation>
|
||||
{t => t(item.title ?? item.id)}
|
||||
</Translation>,
|
||||
disabled: item.disabled,
|
||||
children: item.children,
|
||||
}
|
||||
})
|
||||
|
||||
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 ActionMenuItems = [
|
||||
{
|
||||
key: "account",
|
||||
label: <>
|
||||
<Icons.User />
|
||||
<Translation>
|
||||
{t => t("Account")}
|
||||
</Translation>
|
||||
</>,
|
||||
},
|
||||
{
|
||||
type: "divider"
|
||||
},
|
||||
{
|
||||
key: "switch_account",
|
||||
label: <>
|
||||
<Icons.MdSwitchAccount />
|
||||
<Translation>
|
||||
{t => t("Switch account")}
|
||||
</Translation>
|
||||
</>,
|
||||
},
|
||||
{
|
||||
key: "logout",
|
||||
label: <>
|
||||
<Icons.LogOut />
|
||||
<Translation>
|
||||
{t => t("Logout")}
|
||||
</Translation>
|
||||
</>,
|
||||
danger: true
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
}
|
||||
|
||||
const handleRenderIcon = (icon) => {
|
||||
if (typeof icon === "undefined") {
|
||||
return null
|
||||
}
|
||||
return createIconRender(icon)
|
||||
}
|
||||
]
|
||||
|
||||
export default class Sidebar extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
state = {
|
||||
visible: false,
|
||||
expanded: false,
|
||||
|
||||
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,
|
||||
updateBottomItemProps: (id, newProps) => {
|
||||
let updatedValue = this.state.bottomItems
|
||||
|
||||
updatedValue = updatedValue.map((item) => {
|
||||
if (item.id === id) {
|
||||
item.props = {
|
||||
...item.props,
|
||||
...newProps,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.setState({
|
||||
bottomItems: updatedValue
|
||||
})
|
||||
},
|
||||
attachBottomItem: (id, children, options) => {
|
||||
if (!id) {
|
||||
throw new Error("ID is required")
|
||||
}
|
||||
|
||||
if (!children) {
|
||||
throw new Error("Children is required")
|
||||
}
|
||||
|
||||
if (this.state.bottomItems.find((item) => item.id === id)) {
|
||||
throw new Error("Item already exists")
|
||||
}
|
||||
|
||||
let updatedValue = this.state.bottomItems
|
||||
|
||||
updatedValue.push({
|
||||
id,
|
||||
children,
|
||||
...options
|
||||
})
|
||||
|
||||
this.setState({
|
||||
bottomItems: updatedValue
|
||||
})
|
||||
},
|
||||
removeBottomItem: (id) => {
|
||||
let updatedValue = this.state.bottomItems
|
||||
|
||||
updatedValue = updatedValue.filter((item) => item.id !== id)
|
||||
|
||||
this.setState({
|
||||
bottomItems: updatedValue
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.state = {
|
||||
visible: false,
|
||||
elevated: false,
|
||||
expanded: false,
|
||||
dropdownOpen: false,
|
||||
pathResolvers: null,
|
||||
menus: null,
|
||||
|
||||
customRenderTitle: null,
|
||||
customRender: null,
|
||||
|
||||
bottomItems: [],
|
||||
}
|
||||
|
||||
// handle sidedrawer open/close
|
||||
window.app.eventBus.on("sidedrawer.hasDrawers", () => {
|
||||
this.toggleElevation(true)
|
||||
})
|
||||
window.app.eventBus.on("sidedrawer.noDrawers", () => {
|
||||
this.toggleElevation(false)
|
||||
})
|
||||
topItems: generateTopItems(),
|
||||
bottomItems: [],
|
||||
}
|
||||
|
||||
sidebarRef = React.createRef()
|
||||
|
||||
collapseDebounce = null
|
||||
|
||||
componentDidMount = async () => {
|
||||
await this.loadItems()
|
||||
interface = window.app.layout.sidebar = {
|
||||
toggleVisibility: this.toggleVisibility,
|
||||
toggleCollapse: this.toggleExpanded,
|
||||
isVisible: () => this.state.visible,
|
||||
isExpanded: () => this.state.expanded,
|
||||
updateBottomItemProps: (id, newProps) => {
|
||||
let updatedValue = this.state.bottomItems
|
||||
|
||||
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)
|
||||
updatedValue = updatedValue.map((item) => {
|
||||
if (item.id === id) {
|
||||
item.props = {
|
||||
...item.props,
|
||||
...newProps,
|
||||
}
|
||||
}
|
||||
{
|
||||
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) {
|
||||
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}
|
||||
disabled={item.disabled ?? false}
|
||||
>
|
||||
{this.renderMenuItems(item.children)}
|
||||
</Menu.SubMenu>
|
||||
this.setState({
|
||||
bottomItems: updatedValue
|
||||
})
|
||||
},
|
||||
attachBottomItem: (id, children, options) => {
|
||||
if (!id) {
|
||||
throw new Error("ID is required")
|
||||
}
|
||||
|
||||
return <Menu.Item
|
||||
key={item.id}
|
||||
icon={handleRenderIcon(item.icon)}
|
||||
disabled={item.disabled ?? false}
|
||||
{...item.props}
|
||||
>
|
||||
<Translation>
|
||||
{t => t(item.title ?? item.id)}
|
||||
</Translation>
|
||||
</Menu.Item>
|
||||
})
|
||||
if (!children) {
|
||||
throw new Error("Children is required")
|
||||
}
|
||||
|
||||
if (this.state.bottomItems.find((item) => item.id === id)) {
|
||||
throw new Error("Item already exists")
|
||||
}
|
||||
|
||||
let updatedValue = this.state.bottomItems
|
||||
|
||||
updatedValue.push({
|
||||
id,
|
||||
children,
|
||||
...options
|
||||
})
|
||||
|
||||
this.setState({
|
||||
bottomItems: updatedValue
|
||||
})
|
||||
},
|
||||
removeBottomItem: (id) => {
|
||||
let updatedValue = this.state.bottomItems
|
||||
|
||||
updatedValue = updatedValue.filter((item) => item.id !== id)
|
||||
|
||||
this.setState({
|
||||
bottomItems: updatedValue
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
componentDidMount = async () => {
|
||||
setTimeout(() => {
|
||||
this.toggleVisibility(true)
|
||||
|
||||
if (app.cores.settings.is("sidebar.collapsable", false)) {
|
||||
this.toggleExpanded(true)
|
||||
}
|
||||
}, 10)
|
||||
}
|
||||
|
||||
componentWillUnmount = () => {
|
||||
delete app.layout.sidebar
|
||||
}
|
||||
|
||||
handleClick = (e) => {
|
||||
@ -309,13 +187,9 @@ export default class Sidebar extends React.Component {
|
||||
|
||||
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.location.push(`/${this.state.pathResolvers[e.key]}`, 150)
|
||||
}
|
||||
}
|
||||
const item = sidebarItems.find((item) => item.id === e.key)
|
||||
|
||||
return window.app.location.push(`/${e.key}`, 150)
|
||||
return window.app.location.push(`/${item.path ?? e.key}`, 150)
|
||||
}
|
||||
|
||||
toggleExpanded = (to, force) => {
|
||||
@ -337,7 +211,7 @@ export default class Sidebar extends React.Component {
|
||||
if (!to) {
|
||||
this.collapseDebounce = setTimeout(() => {
|
||||
this.setState({ expanded: to })
|
||||
}, window.app.cores.settings.get("autoCollapseDelay") ?? 500)
|
||||
}, window.app.cores.settings.get("sidebar.collapse_delay_time") ?? 500)
|
||||
} else {
|
||||
this.setState({ expanded: to })
|
||||
}
|
||||
@ -349,14 +223,16 @@ export default class Sidebar extends React.Component {
|
||||
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
|
||||
if (window.app.cores.settings.is("sidebar.collapsable", false)) {
|
||||
if (!this.state.expanded) {
|
||||
this.toggleExpanded(true)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.toggleExpanded(true)
|
||||
}
|
||||
@ -364,7 +240,7 @@ export default class Sidebar extends React.Component {
|
||||
handleMouseLeave = () => {
|
||||
if (!this.state.visible) return
|
||||
|
||||
if (window.app.cores.settings.is("collapseOnLooseFocus", false)) return
|
||||
if (window.app.cores.settings.is("sidebar.collapsable", false)) return
|
||||
|
||||
this.toggleExpanded(false)
|
||||
}
|
||||
@ -378,42 +254,6 @@ export default class Sidebar extends React.Component {
|
||||
this.setState({ dropdownOpen: to })
|
||||
}
|
||||
|
||||
generateDropdownItems = () => {
|
||||
return [
|
||||
{
|
||||
key: "account",
|
||||
label: <>
|
||||
<Icons.User />
|
||||
<Translation>
|
||||
{t => t("Account")}
|
||||
</Translation>
|
||||
</>,
|
||||
},
|
||||
{
|
||||
type: "divider"
|
||||
},
|
||||
{
|
||||
key: "switch_account",
|
||||
label: <>
|
||||
<Icons.MdSwitchAccount />
|
||||
<Translation>
|
||||
{t => t("Switch account")}
|
||||
</Translation>
|
||||
</>,
|
||||
},
|
||||
{
|
||||
key: "logout",
|
||||
label: <>
|
||||
<Icons.LogOut />
|
||||
<Translation>
|
||||
{t => t("Logout")}
|
||||
</Translation>
|
||||
</>,
|
||||
danger: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
onClickDropdownItem = (item) => {
|
||||
const handler = onClickHandlers[item.key]
|
||||
|
||||
@ -423,123 +263,128 @@ export default class Sidebar extends React.Component {
|
||||
}
|
||||
|
||||
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}
|
||||
return <Motion style={{
|
||||
x: spring(!this.state.visible ? 100 : 0),
|
||||
}}>
|
||||
{({ x }) => {
|
||||
return <div
|
||||
className="app_sidebar_wrapper"
|
||||
style={{
|
||||
transform: `translateX(-${x}%)`,
|
||||
}}
|
||||
>
|
||||
{this.state.customRender}
|
||||
</CustomRender>
|
||||
}
|
||||
<div
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
className={classnames(
|
||||
"app_sidebar",
|
||||
{
|
||||
["expanded"]: this.state.visible && this.state.expanded,
|
||||
["hidden"]: !this.state.visible,
|
||||
}
|
||||
)
|
||||
}
|
||||
ref={this.sidebarRef}
|
||||
>
|
||||
|
||||
{
|
||||
!this.state.customRender && <>
|
||||
<div className="app_sidebar_header">
|
||||
<div className="app_sidebar_header_logo">
|
||||
<img src={config.logo?.alt} />
|
||||
<div className="app_sidebar_header">
|
||||
<div className="app_sidebar_header_logo">
|
||||
<img src={config.logo?.alt} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div key="menu" className="app_sidebar_menu_wrapper">
|
||||
<Menu
|
||||
selectable={true}
|
||||
mode="inline"
|
||||
onClick={this.handleClick}
|
||||
defaultSelectedKeys={[defaultSelectedKey]}
|
||||
<div key="menu" className="app_sidebar_menu_wrapper">
|
||||
<Menu
|
||||
mode="inline"
|
||||
onClick={this.handleClick}
|
||||
defaultSelectedKeys={[defaultSelectedKey]}
|
||||
items={this.state.topItems}
|
||||
selectable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
key="bottom"
|
||||
className={classnames(
|
||||
"app_sidebar_menu_wrapper",
|
||||
"bottom"
|
||||
)}
|
||||
>
|
||||
{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}>
|
||||
{
|
||||
this.state.bottomItems.map((item) => {
|
||||
if (item.noContainer) {
|
||||
return React.createElement(item.children, item.childrenProps)
|
||||
}
|
||||
|
||||
return <Menu.Item
|
||||
key={item.id}
|
||||
className="extra_bottom_item"
|
||||
icon={handleRenderIcon(item.icon)}
|
||||
disabled={item.disabled ?? false}
|
||||
{...item.containerProps}
|
||||
>
|
||||
{
|
||||
React.createElement(item.children, item.childrenProps)
|
||||
<Menu
|
||||
selectable={false}
|
||||
mode="inline"
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
{
|
||||
this.state.bottomItems.map((item) => {
|
||||
if (item.noContainer) {
|
||||
return React.createElement(item.children, item.childrenProps)
|
||||
}
|
||||
</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 && <Dropdown
|
||||
menu={{
|
||||
items: this.generateDropdownItems(),
|
||||
onClick: this.onClickDropdownItem
|
||||
}}
|
||||
autoFocus
|
||||
placement="top"
|
||||
trigger={["click"]}
|
||||
onOpenChange={this.onDropdownOpenChange}
|
||||
>
|
||||
<Menu.Item
|
||||
key="account"
|
||||
className="user_avatar"
|
||||
ignoreClick
|
||||
>
|
||||
<Avatar shape="square" src={app.userData?.avatar} />
|
||||
</Menu.Item>
|
||||
</Dropdown>
|
||||
}
|
||||
{
|
||||
!app.userData && <Menu.Item key="login" icon={<Icons.LogIn />}>
|
||||
|
||||
return <Menu.Item
|
||||
key={item.id}
|
||||
className="extra_bottom_item"
|
||||
icon={createIconRender(item.icon)}
|
||||
disabled={item.disabled ?? false}
|
||||
{...item.containerProps}
|
||||
>
|
||||
{
|
||||
React.createElement(item.children, item.childrenProps)
|
||||
}
|
||||
</Menu.Item>
|
||||
})
|
||||
}
|
||||
<Menu.Item key="search" icon={<Icons.Search />} >
|
||||
<Translation>
|
||||
{t => t("Login")}
|
||||
{(t) => t("Search")}
|
||||
</Translation>
|
||||
</Menu.Item>
|
||||
}
|
||||
</Menu>
|
||||
<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 && <Dropdown
|
||||
menu={{
|
||||
items: ActionMenuItems,
|
||||
onClick: this.onClickDropdownItem
|
||||
}}
|
||||
autoFocus
|
||||
placement="top"
|
||||
trigger={["click"]}
|
||||
onOpenChange={this.onDropdownOpenChange}
|
||||
>
|
||||
<Menu.Item
|
||||
key="account"
|
||||
className="user_avatar"
|
||||
ignoreClick
|
||||
>
|
||||
<Avatar shape="square" src={app.userData?.avatar} />
|
||||
</Menu.Item>
|
||||
</Dropdown>
|
||||
}
|
||||
|
||||
{
|
||||
!app.userData && <Menu.Item key="login" icon={<Icons.LogIn />}>
|
||||
<Translation>
|
||||
{t => t("Login")}
|
||||
</Translation>
|
||||
</Menu.Item>
|
||||
}
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}}
|
||||
</Motion>
|
||||
}
|
||||
}
|
@ -1,20 +1,32 @@
|
||||
@import "theme/vars.less";
|
||||
|
||||
.app_sidebar_wrapper {
|
||||
position: relative;
|
||||
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
z-index: 1000;
|
||||
|
||||
width: fit-content;
|
||||
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.app_sidebar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
z-index: 1000;
|
||||
overflow: hidden;
|
||||
|
||||
width: @app_sidebar_width;
|
||||
min-width: @app_sidebar_width;
|
||||
width: @sidebar_width;
|
||||
min-width: @sidebar_width;
|
||||
|
||||
height: 100dvh;
|
||||
height: 100%;
|
||||
|
||||
padding: 10px 0;
|
||||
|
||||
@ -22,16 +34,14 @@
|
||||
|
||||
background-color: var(--sidebar-background-color);
|
||||
|
||||
border-radius: 0 @app_sidebar_borderRadius @app_sidebar_borderRadius 0;
|
||||
border-radius: @sidebar_borderRadius;
|
||||
box-shadow: @card-shadow;
|
||||
|
||||
//border-radius: 0 @sidebar_borderRadius @sidebar_borderRadius 0;
|
||||
border: 1px solid var(--sidebar-background-color);
|
||||
|
||||
&.floating {
|
||||
position: absolute;
|
||||
box-shadow: 0 0 5px 4px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
width: @app_sidebar_width_expanded;
|
||||
width: @sidebar_width_expanded;
|
||||
|
||||
.app_sidebar_menu_wrapper {
|
||||
.ant-menu {
|
||||
@ -50,34 +60,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.customRender {
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
min-width: 34vw;
|
||||
|
||||
padding: 20px;
|
||||
max-width: 60vw;
|
||||
|
||||
.app_sidebar_menu_wrapper {
|
||||
animation: disappear 150ms ease-out forwards;
|
||||
}
|
||||
|
||||
.render_content_wrapper {
|
||||
animation: appear 150ms ease-out forwards;
|
||||
}
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
flex: 0 !important;
|
||||
min-width: 0 !important;
|
||||
background-color: transparent !important;
|
||||
width: 0 !important;
|
||||
}
|
||||
|
||||
&.elevated {
|
||||
box-shadow: 0 0 5px 4px rgba(0, 0, 0, 0.1) !important;
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.app_sidebar_header {
|
||||
@ -154,7 +140,7 @@
|
||||
|
||||
width: 100%;
|
||||
|
||||
padding: 0 0 0 calc(@app_sidebar_width / 2);
|
||||
padding: 0 0 0 calc(@sidebar_width / 2);
|
||||
margin: 5px 0;
|
||||
|
||||
transition: all 150ms ease-in-out;
|
||||
@ -191,50 +177,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.render_content_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
opacity: 0;
|
||||
transition: all 150ms ease-in-out;
|
||||
|
||||
overflow-y: hidden;
|
||||
|
||||
.render_content_header {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.render_content_header_title {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
|
||||
align-items: center;
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
font-size: 1.8;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.render_content {
|
||||
height: 100%;
|
||||
|
||||
padding: 20px;
|
||||
padding-left: 0;
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
}
|
@ -467,11 +467,11 @@ export default class Player extends Core {
|
||||
}
|
||||
case "minimized": {
|
||||
if (change.object.minimized) {
|
||||
app.SidebarController.attachBottomItem("player", BackgroundMediaPlayer, {
|
||||
app.layout.sidebar.attachBottomItem("player", BackgroundMediaPlayer, {
|
||||
noContainer: true
|
||||
})
|
||||
} else {
|
||||
app.SidebarController.removeBottomItem("player")
|
||||
app.layout.sidebar.removeBottomItem("player")
|
||||
}
|
||||
|
||||
app.eventBus.emit("player.minimized.update", change.object.minimized)
|
||||
|
@ -313,8 +313,8 @@ export default class StreamViewer extends React.Component {
|
||||
to = !this.state.cinemaMode
|
||||
}
|
||||
|
||||
if (app.SidebarController) {
|
||||
app.SidebarController.toggleVisibility(!to)
|
||||
if (app.layout.sidebar) {
|
||||
app.layout.sidebar.toggleVisibility(!to)
|
||||
}
|
||||
|
||||
this.setState({ cinemaMode: to })
|
||||
|
@ -601,8 +601,8 @@ export default class SyncLyrics extends React.Component {
|
||||
colorAnalysis,
|
||||
})
|
||||
|
||||
if (app.SidebarController) {
|
||||
app.SidebarController.toggleVisibility(false)
|
||||
if (app.layout.sidebar) {
|
||||
app.layout.sidebar.toggleVisibility(false)
|
||||
}
|
||||
|
||||
if (app.layout.floatingStack) {
|
||||
@ -646,8 +646,8 @@ export default class SyncLyrics extends React.Component {
|
||||
|
||||
delete window._hacks
|
||||
|
||||
if (app.SidebarController) {
|
||||
app.SidebarController.toggleVisibility(true)
|
||||
if (app.layout.sidebar) {
|
||||
app.layout.sidebar.toggleVisibility(true)
|
||||
}
|
||||
|
||||
if (app.layout.floatingStack) {
|
||||
|
@ -213,10 +213,6 @@ html {
|
||||
padding: var(--layoutPadding);
|
||||
|
||||
background-color: transparent;
|
||||
|
||||
&.floating-sidebar {
|
||||
margin-left: @app_sidebar_width;
|
||||
}
|
||||
}
|
||||
|
||||
.root_background {
|
||||
|
@ -1,14 +1,8 @@
|
||||
@app_sidebar_width: 80px;
|
||||
@app_sidebar_width_expanded: 230px;
|
||||
@sidebar_width: 80px;
|
||||
@sidebar_width_expanded: 230px;
|
||||
@sidebar_padding: 10px;
|
||||
@sidebar_borderRadius: 18px;
|
||||
|
||||
// borders & radius
|
||||
@app_sidebar_borderRadius: 18px;
|
||||
|
||||
@app_menuItemSize: 100px;
|
||||
@app_menuItemIconSize: 30px;
|
||||
@app_menuItemTextSize: 12px;
|
||||
|
||||
// TRANSITIONS
|
||||
@transition-ease-in: all 0.3s ease-out;
|
||||
@transition-ease-out: all 0.3s ease-out;
|
||||
@transition-ease-inout: all 150ms ease-in-out;
|
||||
@ -20,5 +14,5 @@
|
||||
@top_bar_height: 52px;
|
||||
@top_bar_padding: 10px;
|
||||
|
||||
@app_bottomBar_iconSize: 45px;
|
||||
@app_topBar_height: 52px;
|
||||
@bottomBar_iconSize: 45px;
|
||||
@topBar_height: 52px;
|
Loading…
x
Reference in New Issue
Block a user