diff --git a/packages/app/src/components/Layout/sidebar/index.jsx b/packages/app/src/components/Layout/sidebar/index.jsx
index 98af04ef..4d6357bd 100755
--- a/packages/app/src/components/Layout/sidebar/index.jsx
+++ b/packages/app/src/components/Layout/sidebar/index.jsx
@@ -1,59 +1,99 @@
import React from "react"
-import { Layout, Menu, Avatar } from "antd"
+import { Menu, Avatar } from "antd"
+import { Translation } from "react-i18next"
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()
},
}
+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 = {}
+
+ const keys = window.app?.settings.get("sidebarKeys") ?? defaultSidebarItems
+
+ // filter undefined components to avoid error
+ keys.filter((key) => {
+ if (typeof components[key] !== "undefined") {
+ return true
+ }
+ })
+
+ keys.forEach((key, index) => {
+ const component = components[key]
+
+ try {
+ // avoid if item is duplicated
+ if (itemsMap.includes(component)) {
+ return false
+ }
+
+ if (typeof component.path !== "undefined") {
+ pathResolvers[component.id] = component.path
+ }
+
+ itemsMap.push(component)
+ } catch (error) {
+ return console.log(error)
+ }
+ })
+
+ return {
+ itemsMap,
+ pathResolvers,
+ }
+}
+
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,
+ toggleCollapse: this.toggleCollapse,
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: [],
+ collapsed: window.app.settings.get("collapseOnLooseFocus") ?? false,
+ pathResolvers: null,
+ menus: null,
}
- 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)
@@ -66,112 +106,21 @@ export default class Sidebar extends React.Component {
collapseDebounce = null
componentDidMount = async () => {
- await this.loadSidebarItems()
+ await this.loadItems()
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)
- }
- })
+ loadItems = async () => {
+ const generation = generateItems()
// update states
- this.setState({ items, menus: itemsMap, loading: false })
+ await this.setState({
+ menus: generation.itemsMap,
+ pathResolvers: generation.pathResolvers,
+ })
}
renderMenuItems(items) {
@@ -184,29 +133,25 @@ export default class Sidebar extends React.Component {
return items.map((item) => {
if (Array.isArray(item.children)) {
- return (
-
-
- {t => t(item.title)}
-
- }
- {...item.props}
- >
- {this.renderMenuItems(item.children)}
-
- )
+ return
+
+ {t => t(item.title)}
+
+ }
+ {...item.props}
+ >
+ {this.renderMenuItems(item.children)}
+
}
- return (
-
-
- {t => t(item.title ?? item.id)}
-
-
- )
+ return
+
+ {t => t(item.title ?? item.id)}
+
+
})
}
@@ -223,27 +168,14 @@ export default class Sidebar extends React.Component {
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()
+ if (typeof this.state.pathResolvers === "object") {
+ if (typeof this.state.pathResolvers[e.key] !== "undefined") {
+ return window.app.setLocation(`/${this.state.pathResolvers[e.key]}`, 150)
}
}
- this.setState({ editMode: to, collapsed: false })
+ return window.app.setLocation(`/${e.key}`, 150)
}
toggleCollapse = (to) => {
@@ -261,11 +193,12 @@ export default class Sidebar extends React.Component {
}
onMouseEnter = () => {
- if (window.app.settings.is("collapseOnLooseFocus", false)) {
- return false
- }
+ if (!this.state.visible) return
+
+ if (window.app.settings.is("collapseOnLooseFocus", false)) return
clearTimeout(this.collapseDebounce)
+
this.collapseDebounce = null
if (this.state.collapsed) {
@@ -274,49 +207,29 @@ export default class Sidebar extends React.Component {
}
handleMouseLeave = () => {
- if (window.app.settings.is("collapseOnLooseFocus", false)) {
- return false
- }
+ 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)
}
}
- 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 {item.children}
- })
- }
-
render() {
- if (this.state.loading) return null
-
- const { user } = this.props
+ if (!this.state.menus) return null
return (
- this.props.onCollapse()}
className={
classnames(
- "sidebar",
+ "app_sidebar",
{
- ["edit_mode"]: this.state.editMode,
+ ["collapsed"]: this.state.visible && this.state.collapsed,
+ ["elevated"]: this.state.visible && this.state.elevated,
["hidden"]: !this.state.visible,
- ["elevated"]: this.state.elevated
}
)
}
@@ -327,70 +240,49 @@ export default class Sidebar extends React.Component {
- {this.state.editMode && (
-
-
-
- )}
+
+
+
- {!this.state.editMode && (
-
-
-
- )}
-
- {!this.state.editMode &&
- {this.state.additionalElements}
-
}
-
- {!this.state.editMode && (
-
-
+
+
)
}
}
\ No newline at end of file
diff --git a/packages/app/src/components/Layout/sidebar/index.less b/packages/app/src/components/Layout/sidebar/index.less
index c7f7df63..05a4518e 100755
--- a/packages/app/src/components/Layout/sidebar/index.less
+++ b/packages/app/src/components/Layout/sidebar/index.less
@@ -1,18 +1,45 @@
@import "theme/vars.less";
-// SIDEBAR
-.ant-layout-sider {
+.app_sidebar {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+
+ left: 0;
+ top: 0;
+
z-index: 1000;
-
- background: var(--sidebar-background-color) !important;
- background-color: var(--sidebar-background-color) !important;
-
- border-radius: 0 @app_sidebar_borderRadius @app_sidebar_borderRadius 0;
overflow: hidden;
- border: 1px solid var(--sidebar-background-color);
+
+ width: @app_sidebar_collapsed_width;
+ min-width: @app_sidebar_width;
+
+ height: 100vh;
+
+ padding: 10px 0;
transition: all 150ms ease-in-out;
+ background-color: var(--sidebar-background-color);
+
+ border-radius: 0 @app_sidebar_borderRadius @app_sidebar_borderRadius 0;
+ border: 1px solid var(--sidebar-background-color);
+
+ &.collapsed {
+ width: 80px;
+ min-width: 80px;
+
+ .app_sidebar_menu_wrapper {
+ .ant-menu {
+ .ant-menu-item:not(.user_avatar) {
+ .ant-menu-title-content {
+ animation: disappear 0.3s ease-out forwards;
+ }
+ }
+ }
+ }
+ }
+
&.hidden {
flex: 0 !important;
min-width: 0 !important;
@@ -23,138 +50,128 @@
&.elevated {
box-shadow: 0 0 5px 4px rgba(0, 0, 0, 0.1) !important;
}
-}
-.ant-menu-item {
- color: var(--background-color-contrast);
+ .app_sidebar_header {
+ display: flex;
+ flex-direction: column;
- h1,
- h2,
- h3,
- h4,
- h5,
- h6,
- span,
- p {
- color: var(--background-color-contrast);
+ height: 17%;
+
+ padding: 10px 0;
+
+ .app_sidebar_header_logo {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ img {
+ user-select: none;
+ --webkit-user-select: none;
+
+ width: 80%;
+ max-height: 80px;
+ }
+
+ &.collapsed {
+ img {
+ max-width: 40px;
+ }
+ }
+ }
}
-}
-.ant-menu,
-.ant-menu ul {
- background: transparent !important;
- background-color: transparent !important;
+ .app_sidebar_menu_wrapper {
+ height: 65%;
+ width: 100%;
- border-right: 0 !important;
-}
+ overflow: overlay;
+ overflow-x: hidden;
-.sidebar .ant-layout-sider-children {
- margin-top: 15px !important;
- margin-bottom: 15px !important;
+ display: flex;
+ flex-direction: column;
- background: transparent !important;
- background-color: transparent !important;
+ align-items: center;
- user-select: none;
- --webkit-user-select: none;
+ transition: all 150ms ease-in-out;
- transition: all 150ms ease-in-out;
- height: 100%;
- display: flex;
- flex-direction: column;
+ &.bottom {
+ position: absolute;
- &.edit_mode .ant-layout-sider-children {
- background: transparent !important;
- background-color: transparent !important;
+ bottom: 0;
+ left: 0;
- margin-top: 15px !important;
+ height: fit-content;
- .app_sidebar_menu_wrapper {
- opacity: 0;
- height: 0;
- overflow: hidden;
+ padding-bottom: 30px;
+ }
+
+ .ant-menu {
+ display: flex;
+ flex-direction: column;
+
+ justify-content: center;
+ align-items: center;
+
+ width: 100%;
+
+ transition: all 150ms ease-in-out;
+
+ .ant-menu-item {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ width: 90%;
+
+ padding: 0 10px !important;
+ margin: 5px 0 !important;
+
+ transition: all 150ms ease-in-out;
+
+ .ant-menu-title-content {
+ flex: 1;
+ text-overflow: clip;
+ }
+
+ &.user_avatar {
+ .ant-menu-title-content {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: fit-content;
+ }
+
+ margin: 0;
+ padding: 0;
+ }
+
+ svg {
+ width: fit-content;
+ margin: 0 !important;
+
+ height: 16px;
+ }
+ }
}
}
}
-.app_sidebar_menu_wrapper {
- transition: all 450ms ease-in-out;
-
- height: 100%;
- width: 100%;
-}
-
-.app_sidebar_header {
- background: transparent !important;
- background-color: transparent !important;
-
- user-select: none;
- --webkit-user-select: none;
-
- display: flex;
- flex-direction: column;
- height: 15%;
- margin-top: 5%;
- margin-bottom: 5%;
-}
-
-.app_sidebar_header_logo {
- user-select: none;
- --webkit-user-select: none;
-
- display: flex;
- align-items: center;
- justify-content: center;
-
- img {
- user-select: none;
- --webkit-user-select: none;
-
- width: 80%;
- max-height: 80px;
+@keyframes disappear {
+ 0% {
+ opacity: 1;
+ width: 100%;
}
- &.collapsed {
- img {
- max-width: 40px;
- }
+ 95% {
+ opacity: 0;
+ width: 0px;
+ margin: 0;
}
-}
-.app_sidebar_menu {
- background: transparent !important;
- background-color: transparent !important;
-
- height: 65%;
- overflow: overlay;
- overflow-x: hidden;
-}
-
-.app_sidebar_bottom {
- position: absolute;
- bottom: 0;
- padding-bottom: 30px;
- z-index: 50;
- left: 0;
-
- background: transparent !important;
- background-color: transparent !important;
- backdrop-filter: blur(10px);
- --webkit-backdrop-filter: blur(10px);
-
- width: 100%;
- height: fit-content;
-
- .ant-menu,
- ul {
- background: transparent !important;
- background-color: transparent !important;
+ 100% {
+ opacity: 0;
+ width: 0px;
+ margin: 0;
+ flex: 0;
}
-}
-
-.user_avatar {
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 0 !important;
}
\ No newline at end of file
diff --git a/packages/app/src/theme/fixments.less b/packages/app/src/theme/fixments.less
index 46c64e77..007e4654 100755
--- a/packages/app/src/theme/fixments.less
+++ b/packages/app/src/theme/fixments.less
@@ -172,6 +172,29 @@
}
// fix menu colors
+.ant-menu,
+.ant-menu ul {
+ background: transparent !important;
+ background-color: transparent !important;
+
+ border-right: 0 !important;
+
+ .ant-menu-item {
+ color: var(--background-color-contrast);
+
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6,
+ span,
+ p {
+ color: var(--background-color-contrast);
+ }
+ }
+}
+
.ant-menu-item {
color: var(--text-color);
border-radius: 8px;
@@ -391,6 +414,6 @@
// fix adm cards
.adm-card {
background: var(--background-color-accent);
-
+
color: var(--text-color);
}
\ No newline at end of file
diff --git a/packages/app/src/theme/index.less b/packages/app/src/theme/index.less
index ad8869b8..e917bed7 100755
--- a/packages/app/src/theme/index.less
+++ b/packages/app/src/theme/index.less
@@ -119,29 +119,25 @@ html {
}
}
-.ant-layout,
-.content_layout {
- width: 100%;
- height: 100%;
- max-height: 100vh;
-
- background-color: transparent;
-}
-
.app_layout {
- background-color: rgba(var(--layoutBackgroundColor), var(--backgroundColorTransparency)) !important;
- backdrop-filter: blur(var(--backgroundBlur));
-
position: relative;
- -webkit-overflow-scrolling: touch;
width: 100%;
height: 100%;
max-height: 100vh;
overflow: hidden;
+
+ display: flex;
+ flex-direction: row;
+
+ background-color: rgba(var(--layoutBackgroundColor), var(--backgroundColorTransparency)) !important;
+ backdrop-filter: blur(var(--backgroundBlur));
+
transition: all 150ms ease-in-out;
+ -webkit-overflow-scrolling: touch;
+
::-webkit-scrollbar {
display: block;
position: absolute;
@@ -164,6 +160,15 @@ html {
}
}
+.ant-layout,
+.content_layout {
+ width: 100%;
+ height: 100%;
+ max-height: 100vh;
+
+ background-color: transparent;
+}
+
.layout_page {
position: relative;
-webkit-overflow-scrolling: touch;
diff --git a/packages/app/src/theme/vars.less b/packages/app/src/theme/vars.less
index 8532cd78..18bf2437 100755
--- a/packages/app/src/theme/vars.less
+++ b/packages/app/src/theme/vars.less
@@ -1,5 +1,8 @@
//* Now this only works as an fallback for unset dynamic theme values
+@app_sidebar_collapsed_width: 80px;
+@app_sidebar_width: 230px;
+
// borders & radius
@app_sidebar_borderRadius: 18px;