improve settings initialization

This commit is contained in:
SrGooglo 2023-01-13 19:07:58 +00:00
parent 739c110b4a
commit f2f3988da6
7 changed files with 691 additions and 656 deletions

View File

@ -2,7 +2,10 @@ import React from "react"
import config from "config"
import { Select } from "antd"
export default [
export default {
icon: "Command",
label: "App",
settings: [
{
"id": "language",
"storaged": true,
@ -161,3 +164,4 @@ export default [
"emitEvent": "router.forceUpdate"
},
]
}

View File

@ -3,7 +3,10 @@ import loadable from "@loadable/component"
import "./index.less"
export default [
export default {
icon: "Eye",
label: "Apparence",
settings: [
{
"id": "compactWidth",
"title": "Compact Width",
@ -167,3 +170,4 @@ export default [
"noUpdate": true,
}
]
}

View File

@ -6,29 +6,9 @@ import ApparenceSettings from "./apparence"
//import ExtensionsSettings from "./extensions"
export default {
app: {
icon: "Command",
label: "App",
settings: AppSettings
},
profile: {
icon: "User",
label: "Profile",
settings: ProfileSettings
},
apparence: {
icon: "Eye",
label: "Apparence",
settings: ApparenceSettings
},
security: {
icon: "Shield",
label: "Security",
settings: SecuritySettings
},
notifications: {
icon: "Bell",
label: "Notifications",
settings: NotificationsSettings
},
app: AppSettings,
profile: ProfileSettings,
apparence: ApparenceSettings,
security: SecuritySettings,
notifications: NotificationsSettings,
}

View File

@ -1,5 +1,9 @@
import React from "react"
export default [
export default {
icon: "Bell",
label: "Notifications",
settings: [
]
}

View File

@ -2,7 +2,17 @@ import React from "react"
import { User } from "models"
import loadable from "@loadable/component"
export default [
export default {
icon: "User",
label: "Profile",
ctxData: async () => {
const userData = await User.data()
return {
userData
}
},
settings: [
{
"id": "username",
"group": "account.basicInfo",
@ -29,9 +39,10 @@ export default [
"allowClear": true,
"placeholder": "Enter your name. e.g. John Doe",
},
"defaultValue": async () => {
const userData = await User.data()
return userData.fullName
"defaultValue": (ctx) => {
console.log(ctx)
return ctx.userData.fullName
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
@ -72,9 +83,8 @@ export default [
"showCount": true,
"maxLength": 320,
},
"defaultValue": async () => {
const userData = await User.data()
return userData.email
"defaultValue": (ctx) => {
return ctx.userData.email
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
@ -99,9 +109,8 @@ export default [
"title": "Avatar",
"description": "Change your avatar (Upload an image or use an URL)",
"component": loadable(() => import("../components/ImageUploader")),
"defaultValue": async () => {
const userData = await User.data()
return userData.avatar
"defaultValue": (ctx) => {
return ctx.userData.avatar
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
@ -127,9 +136,8 @@ export default [
"title": "Cover",
"description": "Change your profile cover (Upload an image or use an URL)",
"component": loadable(() => import("../components/ImageUploader")),
"defaultValue": async () => {
const userData = await User.data()
return userData.cover
"defaultValue": (ctx) => {
return ctx.userData.cover
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
@ -161,9 +169,8 @@ export default [
"showCount": true,
"allowClear": true
},
"defaultValue": async () => {
const userData = await User.data()
return userData.description
"defaultValue": (ctx) => {
return ctx.userData.description
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
@ -182,3 +189,4 @@ export default [
"debounced": true,
},
]
}

View File

@ -3,7 +3,10 @@ import loadable from "@loadable/component"
// TODO: Make logout button require a valid session to be not disabled
export default [
export default {
icon: "Shield",
label: "Security",
settings: [
{
"id": "change-password",
"group": "security.account",
@ -39,3 +42,4 @@ export default [
"emitEvent": "session.logout",
}
]
}

View File

@ -56,7 +56,7 @@ const SettingItem = (props) => {
let { item } = props
const [loading, setLoading] = React.useState(true)
const [value, setValue] = React.useState(item.defaultValue ?? false)
const [value, setValue] = React.useState(null)
const [delayedValue, setDelayedValue] = React.useState(null)
const [disabled, setDisabled] = React.useState(false)
@ -130,15 +130,6 @@ const SettingItem = (props) => {
}
}
const onUnmount = () => {
// unsubscribe eventBus events
if (typeof item.dependsOn === "object") {
for (let key in item.dependsOn) {
window.app.eventBus.off(`setting.update.${key}`, onUpdateItem)
}
}
}
const checkDependsValidation = () => {
return !Boolean(Object.keys(item.dependsOn).every((key) => {
const storagedValue = window.app.settings.get(key)
@ -160,7 +151,11 @@ const SettingItem = (props) => {
}
if (typeof item.defaultValue === "function") {
setValue(await item.defaultValue())
setLoading(true)
setValue(await item.defaultValue(props.ctx))
setLoading(false)
}
if (item.disabled === true) {
@ -196,7 +191,13 @@ const SettingItem = (props) => {
React.useEffect(() => {
settingInitialization()
return onUnmount
return () => {
if (typeof item.dependsOn === "object") {
for (let key in item.dependsOn) {
window.app.eventBus.off(`setting.update.${key}`, onUpdateItem)
}
}
}
}, [])
if (typeof SettingComponent === "string") {
@ -301,7 +302,10 @@ const SettingItem = (props) => {
</div>
<div className="component">
<div>
{loading ? <div> Loading... </div> : React.createElement(SettingComponent, {
{
loading
? <div> Loading... </div>
: React.createElement(SettingComponent, {
...item.props,
ctx: {
currentValue: value,
@ -312,7 +316,8 @@ const SettingItem = (props) => {
})}
</div>
{delayedValue && <div>
{
delayedValue && <div>
<antd.Button
type="round"
icon={<Icons.Save />}
@ -320,18 +325,117 @@ const SettingItem = (props) => {
>
Save
</antd.Button>
</div>}
</div>
}
</div>
</div>
}
const SettingGroup = React.memo((props) => {
const {
ctx,
groupKey,
settings,
loading,
} = props
const fromDecoratorIcon = groupsDecorator[groupKey]?.icon
const fromDecoratorTitle = groupsDecorator[groupKey]?.title
if (loading) {
return <antd.Skeleton active />
}
return <div index={groupKey} key={groupKey} className="group">
<h1>
{
fromDecoratorIcon ? React.createElement(Icons[fromDecoratorIcon]) : null
}
<Translation>
{
t => t(fromDecoratorTitle ?? groupKey)
}
</Translation>
</h1>
<div className="content">
{
settings.map((item) => <SettingItem
item={item}
ctx={ctx}
/>)
}
</div>
</div>
})
const SettingTab = React.memo((props) => {
const { tab } = props
const [loading, setLoading] = React.useState(true)
const [ctxData, setCtxData] = React.useState(null)
let groupsSettings = {}
if (!Array.isArray(tab.settings)) {
console.error("Cannot generate settings from non-array")
return groupsSettings
}
tab.settings.forEach((item) => {
if (!groupsSettings[item.group]) {
groupsSettings[item.group] = []
}
groupsSettings[item.group].push(item)
})
const processCtx = async () => {
setLoading(true)
if (typeof tab.ctxData === "function") {
const resultCtx = await tab.ctxData()
setCtxData(resultCtx)
}
setLoading(false)
}
React.useEffect(() => {
processCtx()
}, [])
return Object.keys(groupsSettings).map((groupKey) => {
return <SettingGroup
groupKey={groupKey}
settings={groupsSettings[groupKey]}
loading={loading}
ctx={ctxData}
/>
})
})
const SettingsTabs = Object.keys(SettingsList).map((settingsKey) => {
const tab = SettingsList[settingsKey]
return {
key: settingsKey,
label: <>
{createIconRender(tab.icon ?? "Settings")}
{tab.label}
</>,
children: <SettingTab
tab={tab}
/>
}
})
export default class SettingsMenu extends React.PureComponent {
state = {
transitionActive: false,
activeKey: "app"
}
componentDidMount() {
componentDidMount = async () => {
if (typeof this.props.close === "function") {
// register escape key to close settings menu
window.addEventListener("keydown", this.handleKeyDown)
@ -350,85 +454,6 @@ export default class SettingsMenu extends React.PureComponent {
}
}
handlePageTransition = (key) => {
this.setState({
transitionActive: true,
})
setTimeout(() => {
this.setState({
activeKey: key
})
setTimeout(() => {
this.setState({
transitionActive: false,
})
}, 100)
}, 100)
}
renderSettings = (key, group) => {
const fromDecoratorIcon = groupsDecorator[key]?.icon
const fromDecoratorTitle = groupsDecorator[key]?.title
return <div className={classnames("fade-opacity-active", { "fade-opacity-leave": this.state.transitionActive })}>
<div key={key} className="group">
<h1>
{fromDecoratorIcon ? React.createElement(Icons[fromDecoratorIcon]) : null}
<Translation>{
t => t(fromDecoratorTitle ?? key)
}</Translation>
</h1>
<div className="content">
{group.map((item) => <SettingItem
item={item}
ctx={{
close: this.props.close
}}
/>)}
</div>
</div>
</div>
}
generateSettingsGroups = (data) => {
let groups = {}
if (!Array.isArray(data)) {
console.error("Cannot generate settings groups from non-array data")
return groups
}
data.forEach((item) => {
if (!groups[item.group]) {
groups[item.group] = []
}
groups[item.group].push(item)
})
return Object.keys(groups).map((groupKey) => {
return this.renderSettings(groupKey, groups[groupKey])
})
}
generateSettingsTabs = () => {
return Object.keys(SettingsList).map((key) => {
return <antd.Tabs.TabPane
key={key}
tab={
<span>
{createIconRender(SettingsList[key].icon)}
{SettingsList[key].label}
</span>
}
>
{this.generateSettingsGroups(SettingsList[key].settings)}
</antd.Tabs.TabPane>
})
}
onClickAppAbout = () => {
window.app.setLocation("/about")
@ -437,22 +462,28 @@ export default class SettingsMenu extends React.PureComponent {
}
}
changeTab = (activeKey) => {
this.setState({ activeKey })
}
render() {
return <div className={
classnames("settings_wrapper", {
return <div
className={classnames(
"settings_wrapper",
{
["mobile"]: window.isMobile,
})
}>
}
)}
>
<div className="settings">
<antd.Tabs
activeKey={this.state.activeKey}
onTabClick={this.handlePageTransition}
onTabClick={this.changeTab}
tabPosition={window.isMobile ? "top" : "left"}
centered={window.isMobile}
destroyInactiveTabPane
>
{this.generateSettingsTabs()}
</antd.Tabs>
items={SettingsTabs}
/>
<SettingsFooter onClickAppAbout={this.onClickAppAbout} />
</div>