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,162 +2,166 @@ import React from "react"
import config from "config" import config from "config"
import { Select } from "antd" import { Select } from "antd"
export default [ export default {
{ icon: "Command",
"id": "language", label: "App",
"storaged": true, settings: [
"group": "general", {
"component": "Select", "id": "language",
"icon": "MdTranslate", "storaged": true,
"title": "Language", "group": "general",
"description": "Choose a language for the application", "component": "Select",
"props": { "icon": "MdTranslate",
children: config.i18n.languages.map((language) => { "title": "Language",
return <Select.Option value={language.locale}>{language.name}</Select.Option> "description": "Choose a language for the application",
}) "props": {
children: config.i18n.languages.map((language) => {
return <Select.Option value={language.locale}>{language.name}</Select.Option>
})
},
"emitEvent": "changeLanguage"
}, },
"emitEvent": "changeLanguage" {
}, "id": "haptic_feedback",
{ "storaged": true,
"id": "haptic_feedback", "group": "general",
"storaged": true, "component": "Switch",
"group": "general", "icon": "MdVibration",
"component": "Switch", "title": "Haptic Feedback",
"icon": "MdVibration", "description": "Enable haptic feedback on touch events.",
"title": "Haptic Feedback", },
"description": "Enable haptic feedback on touch events.", {
}, "id": "selection_longPress_timeout",
{ "storaged": true,
"id": "selection_longPress_timeout", "group": "general",
"storaged": true, "component": "Slider",
"group": "general", "icon": "MdTimer",
"component": "Slider", "title": "Selection press delay",
"icon": "MdTimer", "description": "Set the delay before the selection trigger is activated.",
"title": "Selection press delay", "props": {
"description": "Set the delay before the selection trigger is activated.", min: 300,
"props": { max: 2000,
min: 300, step: 100,
max: 2000, marks: {
step: 100, 300: "0.3s",
marks: { 600: "0.6s",
300: "0.3s", 1000: "1s",
600: "0.6s", 1500: "1.5s",
1000: "1s", 2000: "2s",
1500: "1.5s", }
2000: "2s",
}
}
},
{
"id": "clear_internal_storage",
"storaged": false,
"group": "general",
"component": "Button",
"icon": "MdDelete",
"title": "Clear internal storage",
"description": "Clear all the data stored in the internal storage, including your current session. It will not affect the data stored in the cloud.",
"emitEvent": "app.clearInternalStorage"
},
{
"id": "notifications_sound",
"storaged": true,
"group": "notifications",
"component": "Switch",
"icon": "MdVolumeUp",
"title": "Notifications Sound",
"description": "Play a sound when a notification is received.",
},
{
"id": "notifications_vibrate",
"storaged": true,
"group": "notifications",
"component": "Switch",
"icon": "MdVibration",
"title": "Vibration",
"description": "Vibrate the device when a notification is received.",
"emitEvent": "changeNotificationsVibrate"
},
{
"id": "notifications_sound_volume",
"storaged": true,
"group": "notifications",
"component": "Slider",
"icon": "MdVolumeUp",
"title": "Sound Volume",
"description": "Set the volume of the sound when a notification is received.",
"props": {
tipFormatter: (value) => {
return `${value}%`
} }
}, },
"emitEvent": "changeNotificationsSoundVolume" {
}, "id": "clear_internal_storage",
{ "storaged": false,
"id": "collapseOnLooseFocus", "group": "general",
"storaged": true, "component": "Button",
"group": "sidebar", "icon": "MdDelete",
"component": "Switch", "title": "Clear internal storage",
"icon": "Columns", "description": "Clear all the data stored in the internal storage, including your current session. It will not affect the data stored in the cloud.",
"title": "Auto Collapse", "emitEvent": "app.clearInternalStorage"
"description": "Collapse the sidebar when loose focus",
"emitEvent": "settingChanged.sidebar_collapse",
},
{
"id": "autoCollapseDelay",
"storaged": true,
"group": "sidebar",
"component": "Slider",
"icon": "MdTimer",
"dependsOn": {
"collapseOnLooseFocus": true
}, },
"title": "Auto Collapse timeout", {
"description": "Set the delay before the sidebar is collapsed", "id": "notifications_sound",
"props": { "storaged": true,
min: 0, "group": "notifications",
max: 2000, "component": "Switch",
step: 100, "icon": "MdVolumeUp",
marks: { "title": "Notifications Sound",
0: "No delay", "description": "Play a sound when a notification is received.",
600: "0.6s", },
1000: "1s", {
1500: "1.5s", "id": "notifications_vibrate",
2000: "2s", "storaged": true,
"group": "notifications",
"component": "Switch",
"icon": "MdVibration",
"title": "Vibration",
"description": "Vibrate the device when a notification is received.",
"emitEvent": "changeNotificationsVibrate"
},
{
"id": "notifications_sound_volume",
"storaged": true,
"group": "notifications",
"component": "Slider",
"icon": "MdVolumeUp",
"title": "Sound Volume",
"description": "Set the volume of the sound when a notification is received.",
"props": {
tipFormatter: (value) => {
return `${value}%`
}
},
"emitEvent": "changeNotificationsSoundVolume"
},
{
"id": "collapseOnLooseFocus",
"storaged": true,
"group": "sidebar",
"component": "Switch",
"icon": "Columns",
"title": "Auto Collapse",
"description": "Collapse the sidebar when loose focus",
"emitEvent": "settingChanged.sidebar_collapse",
},
{
"id": "autoCollapseDelay",
"storaged": true,
"group": "sidebar",
"component": "Slider",
"icon": "MdTimer",
"dependsOn": {
"collapseOnLooseFocus": true
},
"title": "Auto Collapse timeout",
"description": "Set the delay before the sidebar is collapsed",
"props": {
min: 0,
max: 2000,
step: 100,
marks: {
0: "No delay",
600: "0.6s",
1000: "1s",
1500: "1.5s",
2000: "2s",
}
} }
}
},
{
"id": "feed_max_fetch",
"title": "Fetch max items",
"description": "Set the maximum number of items to load per fetch in the feed list",
"component": "Slider",
"icon": "MdFormatListNumbered",
"group": "posts",
"props": {
min: 5,
max: 50,
}, },
"storaged": true, {
}, "id": "feed_max_fetch",
{ "title": "Fetch max items",
"id": "postCard_carrusel_auto", "description": "Set the maximum number of items to load per fetch in the feed list",
"title": "Post autoplay", "component": "Slider",
"description": "Automatically play the post medias when the post has multiple medias", "icon": "MdFormatListNumbered",
"component": "Switch", "group": "posts",
"icon": "MdPhotoCameraBack", "props": {
"group": "posts", min: 5,
"storaged": true, max: 50,
"emitEvent": "router.forceUpdate", },
"disabled": true "storaged": true,
}, },
{ {
"id": "postCard_expansible_actions", "id": "postCard_carrusel_auto",
"title": "Expansible actions", "title": "Post autoplay",
"description": "Automatically show or hide the actions bar", "description": "Automatically play the post medias when the post has multiple medias",
"component": "Switch", "component": "Switch",
"icon": "MdCallToAction", "icon": "MdPhotoCameraBack",
"group": "posts", "group": "posts",
"storaged": true, "storaged": true,
"emitEvent": "router.forceUpdate" "emitEvent": "router.forceUpdate",
}, "disabled": true
] },
{
"id": "postCard_expansible_actions",
"title": "Expansible actions",
"description": "Automatically show or hide the actions bar",
"component": "Switch",
"icon": "MdCallToAction",
"group": "posts",
"storaged": true,
"emitEvent": "router.forceUpdate"
},
]
}

View File

@ -3,167 +3,171 @@ import loadable from "@loadable/component"
import "./index.less" import "./index.less"
export default [ export default {
{ icon: "Eye",
"id": "compactWidth", label: "Apparence",
"title": "Compact Width", settings: [
"description": "Sets the width of the app to a compact width to facilitate the vision of components.", {
"component": "Switch", "id": "compactWidth",
"icon": "MdCompress", "title": "Compact Width",
"group": "layout", "description": "Sets the width of the app to a compact width to facilitate the vision of components.",
"experimental": true, "component": "Switch",
"storaged": true "icon": "MdCompress",
}, "group": "layout",
{ "experimental": true,
"id": "sidebar.floating", "storaged": true
"title": "Floating Sidebar", },
"description": "Make the sidebar float over layout content.", {
"component": "Switch", "id": "sidebar.floating",
"icon": "MdOutlineLastPage", "title": "Floating Sidebar",
"group": "layout", "description": "Make the sidebar float over layout content.",
"emitEvent": "app.softReload", "component": "Switch",
"storaged": true "icon": "MdOutlineLastPage",
}, "group": "layout",
{ "emitEvent": "app.softReload",
"id": "reduceAnimations", "storaged": true
"storaged": true, },
"group": "animations", {
"component": "Switch", "id": "reduceAnimations",
"icon": "MdOutlineSlowMotionVideo", "storaged": true,
"title": "Reduce animation", "group": "animations",
"experimental": true "component": "Switch",
}, "icon": "MdOutlineSlowMotionVideo",
{ "title": "Reduce animation",
"id": "pageTransitionDuration", "experimental": true
"storaged": true, },
"group": "animations", {
"component": "Slider", "id": "pageTransitionDuration",
"icon": "MdOutlineSpeed", "storaged": true,
"title": "Page transition duration", "group": "animations",
"description": "Change the duration of the page transition animation.", "component": "Slider",
"props": { "icon": "MdOutlineSpeed",
min: 0, "title": "Page transition duration",
max: 1000, "description": "Change the duration of the page transition animation.",
step: 50, "props": {
tooltip: { min: 0,
formatter: (value) => `${value / 1000}s` max: 1000,
step: 50,
tooltip: {
formatter: (value) => `${value / 1000}s`
}
},
"emitEvent": "modifyTheme",
"emissionValueUpdate": (value) => {
return {
"page-transition-duration": `${value}ms`
}
},
},
{
"id": "auto_darkMode",
"experimental": true,
"storaged": true,
"group": "aspect",
"component": "Switch",
"icon": "Moon",
"title": "Auto dark mode",
"emitEvent": "style.autoDarkModeToogle",
},
{
"experimental": true,
"dependsOn": {
"auto_darkMode": false
},
"id": "darkMode",
"storaged": true,
"group": "aspect",
"component": "Switch",
"icon": "Moon",
"title": "Dark mode",
"emitEvent": "theme.applyVariant",
"emissionValueUpdate": (value) => {
return value ? "dark" : "light"
},
},
{
"id": "primaryColor",
"storaged": true,
"group": "aspect",
"component": "SliderColorPicker",
"title": "Primary color",
"description": "Change primary color of the application.",
"emitEvent": "modifyTheme",
"reloadValueOnUpdateEvent": "resetTheme",
"emissionValueUpdate": (value) => {
return {
primaryColor: value
}
} }
}, },
"emitEvent": "modifyTheme", {
"emissionValueUpdate": (value) => { "id": "backgroundImage",
return { "storaged": true,
"page-transition-duration": `${value}ms` "group": "aspect",
} "title": "Background image",
"description": "Change background image of the application. You can use a local image or a remote image (URL).",
"component": loadable(() => import("../components/ImageUploader")),
"props": {
"noPreview": true,
},
"emitEvent": "modifyTheme",
"emissionValueUpdate": (value) => {
return {
backgroundImage: `url(${value})`
}
},
}, },
}, {
{ "id": "backgroundBlur",
"id": "auto_darkMode", "storaged": true,
"experimental": true, "group": "aspect",
"storaged": true, "component": "Slider",
"group": "aspect", "icon": "Eye",
"component": "Switch", "title": "Background blur",
"icon": "Moon", "description": "Create a blur effect on the background.",
"title": "Auto dark mode", "props": {
"emitEvent": "style.autoDarkModeToogle", min: 0,
}, max: 50,
{ step: 5
"experimental": true, },
"dependsOn": { "emitEvent": "modifyTheme",
"auto_darkMode": false "emissionValueUpdate": (value) => {
return {
backgroundBlur: `${value}px`,
}
},
}, },
"id": "darkMode", {
"storaged": true, "id": "backgroundColorTransparency",
"group": "aspect", "storaged": true,
"component": "Switch", "group": "aspect",
"icon": "Moon", "component": "Slider",
"title": "Dark mode", "icon": "Eye",
"emitEvent": "theme.applyVariant", "title": "Background color transparency",
"emissionValueUpdate": (value) => { "description": "Adjust the transparency of the background color.",
return value ? "dark" : "light" "props": {
min: 0,
max: 1,
step: 0.1
},
"emitEvent": "modifyTheme",
"emissionValueUpdate": (value) => {
return {
backgroundColorTransparency: value,
}
},
}, },
}, {
{ "id": "resetTheme",
"id": "primaryColor", "storaged": true,
"storaged": true, "group": "aspect",
"group": "aspect", "component": "Button",
"component": "SliderColorPicker", "title": "Reset theme",
"title": "Primary color", "props": {
"description": "Change primary color of the application.", "children": "Default Theme"
"emitEvent": "modifyTheme", },
"reloadValueOnUpdateEvent": "resetTheme", "emitEvent": "resetTheme",
"emissionValueUpdate": (value) => { "noUpdate": true,
return {
primaryColor: value
}
} }
}, ]
{ }
"id": "backgroundImage",
"storaged": true,
"group": "aspect",
"title": "Background image",
"description": "Change background image of the application. You can use a local image or a remote image (URL).",
"component": loadable(() => import("../components/ImageUploader")),
"props": {
"noPreview": true,
},
"emitEvent": "modifyTheme",
"emissionValueUpdate": (value) => {
return {
backgroundImage: `url(${value})`
}
},
},
{
"id": "backgroundBlur",
"storaged": true,
"group": "aspect",
"component": "Slider",
"icon": "Eye",
"title": "Background blur",
"description": "Create a blur effect on the background.",
"props": {
min: 0,
max: 50,
step: 5
},
"emitEvent": "modifyTheme",
"emissionValueUpdate": (value) => {
return {
backgroundBlur: `${value}px`,
}
},
},
{
"id": "backgroundColorTransparency",
"storaged": true,
"group": "aspect",
"component": "Slider",
"icon": "Eye",
"title": "Background color transparency",
"description": "Adjust the transparency of the background color.",
"props": {
min: 0,
max: 1,
step: 0.1
},
"emitEvent": "modifyTheme",
"emissionValueUpdate": (value) => {
return {
backgroundColorTransparency: value,
}
},
},
{
"id": "resetTheme",
"storaged": true,
"group": "aspect",
"component": "Button",
"title": "Reset theme",
"props": {
"children": "Default Theme"
},
"emitEvent": "resetTheme",
"noUpdate": true,
}
]

View File

@ -6,29 +6,9 @@ import ApparenceSettings from "./apparence"
//import ExtensionsSettings from "./extensions" //import ExtensionsSettings from "./extensions"
export default { export default {
app: { app: AppSettings,
icon: "Command", profile: ProfileSettings,
label: "App", apparence: ApparenceSettings,
settings: AppSettings security: SecuritySettings,
}, notifications: NotificationsSettings,
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
},
} }

View File

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

View File

@ -2,183 +2,191 @@ import React from "react"
import { User } from "models" import { User } from "models"
import loadable from "@loadable/component" import loadable from "@loadable/component"
export default [ export default {
{ icon: "User",
"id": "username", label: "Profile",
"group": "account.basicInfo", ctxData: async () => {
"component": "Button", const userData = await User.data()
"icon": "AtSign",
"title": "Username", return {
"description": "Your username is the name you use to log in to your account.", userData
"props": { }
"disabled": true,
"children": "Change username",
},
}, },
{ settings: [
"id": "fullName", {
"group": "account.basicInfo", "id": "username",
"component": "Input", "group": "account.basicInfo",
"icon": "Edit3", "component": "Button",
"title": "Name", "icon": "AtSign",
"description": "Change your public name", "title": "Username",
"props": { "description": "Your username is the name you use to log in to your account.",
// set max length "props": {
"maxLength": 120, "disabled": true,
"showCount": true, "children": "Change username",
"allowClear": true, },
"placeholder": "Enter your name. e.g. John Doe",
}, },
"defaultValue": async () => { {
const userData = await User.data() "id": "fullName",
return userData.fullName "group": "account.basicInfo",
}, "component": "Input",
"onUpdate": async (value) => { "icon": "Edit3",
const selfId = await User.selfUserId() "title": "Name",
"description": "Change your public name",
"props": {
// set max length
"maxLength": 120,
"showCount": true,
"allowClear": true,
"placeholder": "Enter your name. e.g. John Doe",
},
"defaultValue": (ctx) => {
console.log(ctx)
const result = window.app.api.withEndpoints("main").post.updateUser({ return ctx.userData.fullName
_id: selfId, },
update: { "onUpdate": async (value) => {
fullName: value const selfId = await User.selfUserId()
const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId,
update: {
fullName: value
}
})
if (result) {
return result
} }
}) },
"extraActions": [
if (result) { {
return result "id": "unset",
} "icon": "Delete",
}, "title": "Unset",
"extraActions": [ "onClick": async () => {
{ window.app.api.withEndpoints("main").post.unsetPublicName()
"id": "unset", }
"icon": "Delete",
"title": "Unset",
"onClick": async () => {
window.app.api.withEndpoints("main").post.unsetPublicName()
} }
} ],
], "debounced": true,
"debounced": true,
},
{
"id": "email",
"group": "account.basicInfo",
"component": "Input",
"icon": "Mail",
"title": "Email",
"description": "Change your email address",
"props": {
"placeholder": "Enter your email address",
"allowClear": true,
"showCount": true,
"maxLength": 320,
}, },
"defaultValue": async () => { {
const userData = await User.data() "id": "email",
return userData.email "group": "account.basicInfo",
}, "component": "Input",
"onUpdate": async (value) => { "icon": "Mail",
const selfId = await User.selfUserId() "title": "Email",
"description": "Change your email address",
"props": {
"placeholder": "Enter your email address",
"allowClear": true,
"showCount": true,
"maxLength": 320,
},
"defaultValue": (ctx) => {
return ctx.userData.email
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
const result = window.app.api.withEndpoints("main").post.updateUser({ const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId, _id: selfId,
update: { update: {
email: value email: value
}
})
if (result) {
return result
} }
}) },
"debounced": true,
if (result) {
return result
}
}, },
"debounced": true, {
}, "id": "avatar",
{ "group": "account.profile",
"id": "avatar", "icon": "Image",
"group": "account.profile", "title": "Avatar",
"icon": "Image", "description": "Change your avatar (Upload an image or use an URL)",
"title": "Avatar", "component": loadable(() => import("../components/ImageUploader")),
"description": "Change your avatar (Upload an image or use an URL)", "defaultValue": (ctx) => {
"component": loadable(() => import("../components/ImageUploader")), return ctx.userData.avatar
"defaultValue": async () => { },
const userData = await User.data() "onUpdate": async (value) => {
return userData.avatar const selfId = await User.selfUserId()
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
const result = window.app.api.withEndpoints("main").post.updateUser({ const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId, _id: selfId,
update: { update: {
avatar: value avatar: value
}
})
if (result) {
app.message.success("Avatar updated")
return result
} }
}) },
"debounced": true,
if (result) {
app.message.success("Avatar updated")
return result
}
}, },
"debounced": true, {
}, "id": "cover",
{ "group": "account.profile",
"id": "cover", "icon": "Image",
"group": "account.profile", "title": "Cover",
"icon": "Image", "description": "Change your profile cover (Upload an image or use an URL)",
"title": "Cover", "component": loadable(() => import("../components/ImageUploader")),
"description": "Change your profile cover (Upload an image or use an URL)", "defaultValue": (ctx) => {
"component": loadable(() => import("../components/ImageUploader")), return ctx.userData.cover
"defaultValue": async () => { },
const userData = await User.data() "onUpdate": async (value) => {
return userData.cover const selfId = await User.selfUserId()
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
const result = window.app.api.withEndpoints("main").post.updateUser({ const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId, _id: selfId,
update: { update: {
cover: value cover: value
}
})
if (result) {
app.message.success("Cover updated")
return result
} }
}) },
"debounced": true,
},
{
"id": "description",
"group": "account.profile",
"component": "TextArea",
"icon": "Edit3",
"title": "Description",
"description": "Change your description for your profile",
"props": {
"placeholder": "Enter here a description for your profile",
"maxLength": 1000,
"showCount": true,
"allowClear": true
},
"defaultValue": (ctx) => {
return ctx.userData.description
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
if (result) { const result = window.app.api.withEndpoints("main").post.updateUser({
app.message.success("Cover updated") _id: selfId,
return result update: {
} description: value
}, }
"debounced": true, })
},
{
"id": "description",
"group": "account.profile",
"component": "TextArea",
"icon": "Edit3",
"title": "Description",
"description": "Change your description for your profile",
"props": {
"placeholder": "Enter here a description for your profile",
"maxLength": 1000,
"showCount": true,
"allowClear": true
},
"defaultValue": async () => {
const userData = await User.data()
return userData.description
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
const result = window.app.api.withEndpoints("main").post.updateUser({ if (result) {
_id: selfId, return result
update: {
description: value
} }
}) },
"debounced": true,
if (result) {
return result
}
}, },
"debounced": true, ]
}, }
]

View File

@ -3,39 +3,43 @@ import loadable from "@loadable/component"
// TODO: Make logout button require a valid session to be not disabled // TODO: Make logout button require a valid session to be not disabled
export default [ export default {
{ icon: "Shield",
"id": "change-password", label: "Security",
"group": "security.account", settings: [
"title": "Change Password", {
"description": "Change your password", "id": "change-password",
"icon": "Lock", "group": "security.account",
"component": loadable(() => import("../components/changePassword")), "title": "Change Password",
}, "description": "Change your password",
{ "icon": "Lock",
"id": "two-factor-authentication", "component": loadable(() => import("../components/changePassword")),
"group": "security.account", },
"title": "Two-Factor Authentication", {
"description": "Add an extra layer of security to your account", "id": "two-factor-authentication",
"icon": "MdOutlineSecurity", "group": "security.account",
"component": "Switch", "title": "Two-Factor Authentication",
}, "description": "Add an extra layer of security to your account",
{ "icon": "MdOutlineSecurity",
"id": "sessions", "component": "Switch",
"group": "security.account", },
"title": "Sessions", {
"description": "Manage your active sessions", "id": "sessions",
"icon": "Monitor", "group": "security.account",
"component": loadable(() => import("../components/sessions")), "title": "Sessions",
"storaged": false "description": "Manage your active sessions",
}, "icon": "Monitor",
{ "component": loadable(() => import("../components/sessions")),
"id": "logout", "storaged": false
"group": "security.other", },
"component": "Button", {
"icon": "LogOut", "id": "logout",
"title": "Logout", "group": "security.other",
"description": "Logout from your account", "component": "Button",
"emitEvent": "session.logout", "icon": "LogOut",
} "title": "Logout",
] "description": "Logout from your account",
"emitEvent": "session.logout",
}
]
}

View File

@ -56,7 +56,7 @@ const SettingItem = (props) => {
let { item } = props let { item } = props
const [loading, setLoading] = React.useState(true) 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 [delayedValue, setDelayedValue] = React.useState(null)
const [disabled, setDisabled] = React.useState(false) 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 = () => { const checkDependsValidation = () => {
return !Boolean(Object.keys(item.dependsOn).every((key) => { return !Boolean(Object.keys(item.dependsOn).every((key) => {
const storagedValue = window.app.settings.get(key) const storagedValue = window.app.settings.get(key)
@ -160,7 +151,11 @@ const SettingItem = (props) => {
} }
if (typeof item.defaultValue === "function") { if (typeof item.defaultValue === "function") {
setValue(await item.defaultValue()) setLoading(true)
setValue(await item.defaultValue(props.ctx))
setLoading(false)
} }
if (item.disabled === true) { if (item.disabled === true) {
@ -196,7 +191,13 @@ const SettingItem = (props) => {
React.useEffect(() => { React.useEffect(() => {
settingInitialization() 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") { if (typeof SettingComponent === "string") {
@ -301,37 +302,140 @@ const SettingItem = (props) => {
</div> </div>
<div className="component"> <div className="component">
<div> <div>
{loading ? <div> Loading... </div> : React.createElement(SettingComponent, { {
...item.props, loading
ctx: { ? <div> Loading... </div>
currentValue: value, : React.createElement(SettingComponent, {
dispatchUpdate, ...item.props,
onUpdateItem, ctx: {
...props.ctx, currentValue: value,
} dispatchUpdate,
})} onUpdateItem,
...props.ctx,
}
})}
</div> </div>
{delayedValue && <div> {
<antd.Button delayedValue && <div>
type="round" <antd.Button
icon={<Icons.Save />} type="round"
onClick={async () => await dispatchUpdate(value)} icon={<Icons.Save />}
> onClick={async () => await dispatchUpdate(value)}
Save >
</antd.Button> Save
</div>} </antd.Button>
</div>
}
</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 { export default class SettingsMenu extends React.PureComponent {
state = { state = {
transitionActive: false,
activeKey: "app" activeKey: "app"
} }
componentDidMount() { componentDidMount = async () => {
if (typeof this.props.close === "function") { if (typeof this.props.close === "function") {
// register escape key to close settings menu // register escape key to close settings menu
window.addEventListener("keydown", this.handleKeyDown) 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 = () => { onClickAppAbout = () => {
window.app.setLocation("/about") window.app.setLocation("/about")
@ -437,22 +462,28 @@ export default class SettingsMenu extends React.PureComponent {
} }
} }
changeTab = (activeKey) => {
this.setState({ activeKey })
}
render() { render() {
return <div className={ return <div
classnames("settings_wrapper", { className={classnames(
["mobile"]: window.isMobile, "settings_wrapper",
}) {
}> ["mobile"]: window.isMobile,
}
)}
>
<div className="settings"> <div className="settings">
<antd.Tabs <antd.Tabs
activeKey={this.state.activeKey} activeKey={this.state.activeKey}
onTabClick={this.handlePageTransition} onTabClick={this.changeTab}
tabPosition={window.isMobile ? "top" : "left"} tabPosition={window.isMobile ? "top" : "left"}
centered={window.isMobile} centered={window.isMobile}
destroyInactiveTabPane destroyInactiveTabPane
> items={SettingsTabs}
{this.generateSettingsTabs()} />
</antd.Tabs>
<SettingsFooter onClickAppAbout={this.onClickAppAbout} /> <SettingsFooter onClickAppAbout={this.onClickAppAbout} />
</div> </div>