improve settings initialization

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

View File

@ -2,162 +2,166 @@ import React from "react"
import config from "config"
import { Select } from "antd"
export default [
{
"id": "language",
"storaged": true,
"group": "general",
"component": "Select",
"icon": "MdTranslate",
"title": "Language",
"description": "Choose a language for the application",
"props": {
children: config.i18n.languages.map((language) => {
return <Select.Option value={language.locale}>{language.name}</Select.Option>
})
export default {
icon: "Command",
label: "App",
settings: [
{
"id": "language",
"storaged": true,
"group": "general",
"component": "Select",
"icon": "MdTranslate",
"title": "Language",
"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,
"group": "general",
"component": "Switch",
"icon": "MdVibration",
"title": "Haptic Feedback",
"description": "Enable haptic feedback on touch events.",
},
{
"id": "selection_longPress_timeout",
"storaged": true,
"group": "general",
"component": "Slider",
"icon": "MdTimer",
"title": "Selection press delay",
"description": "Set the delay before the selection trigger is activated.",
"props": {
min: 300,
max: 2000,
step: 100,
marks: {
300: "0.3s",
600: "0.6s",
1000: "1s",
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}%`
{
"id": "haptic_feedback",
"storaged": true,
"group": "general",
"component": "Switch",
"icon": "MdVibration",
"title": "Haptic Feedback",
"description": "Enable haptic feedback on touch events.",
},
{
"id": "selection_longPress_timeout",
"storaged": true,
"group": "general",
"component": "Slider",
"icon": "MdTimer",
"title": "Selection press delay",
"description": "Set the delay before the selection trigger is activated.",
"props": {
min: 300,
max: 2000,
step: 100,
marks: {
300: "0.3s",
600: "0.6s",
1000: "1s",
1500: "1.5s",
2000: "2s",
}
}
},
"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
{
"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"
},
"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": "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": "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": "postCard_carrusel_auto",
"title": "Post autoplay",
"description": "Automatically play the post medias when the post has multiple medias",
"component": "Switch",
"icon": "MdPhotoCameraBack",
"group": "posts",
"storaged": true,
"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"
},
]
{
"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": "postCard_carrusel_auto",
"title": "Post autoplay",
"description": "Automatically play the post medias when the post has multiple medias",
"component": "Switch",
"icon": "MdPhotoCameraBack",
"group": "posts",
"storaged": true,
"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"
export default [
{
"id": "compactWidth",
"title": "Compact Width",
"description": "Sets the width of the app to a compact width to facilitate the vision of components.",
"component": "Switch",
"icon": "MdCompress",
"group": "layout",
"experimental": true,
"storaged": true
},
{
"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
},
{
"id": "reduceAnimations",
"storaged": true,
"group": "animations",
"component": "Switch",
"icon": "MdOutlineSlowMotionVideo",
"title": "Reduce animation",
"experimental": true
},
{
"id": "pageTransitionDuration",
"storaged": true,
"group": "animations",
"component": "Slider",
"icon": "MdOutlineSpeed",
"title": "Page transition duration",
"description": "Change the duration of the page transition animation.",
"props": {
min: 0,
max: 1000,
step: 50,
tooltip: {
formatter: (value) => `${value / 1000}s`
export default {
icon: "Eye",
label: "Apparence",
settings: [
{
"id": "compactWidth",
"title": "Compact Width",
"description": "Sets the width of the app to a compact width to facilitate the vision of components.",
"component": "Switch",
"icon": "MdCompress",
"group": "layout",
"experimental": true,
"storaged": true
},
{
"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
},
{
"id": "reduceAnimations",
"storaged": true,
"group": "animations",
"component": "Switch",
"icon": "MdOutlineSlowMotionVideo",
"title": "Reduce animation",
"experimental": true
},
{
"id": "pageTransitionDuration",
"storaged": true,
"group": "animations",
"component": "Slider",
"icon": "MdOutlineSpeed",
"title": "Page transition duration",
"description": "Change the duration of the page transition animation.",
"props": {
min: 0,
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) => {
return {
"page-transition-duration": `${value}ms`
}
{
"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": "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": "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": "darkMode",
"storaged": true,
"group": "aspect",
"component": "Switch",
"icon": "Moon",
"title": "Dark mode",
"emitEvent": "theme.applyVariant",
"emissionValueUpdate": (value) => {
return value ? "dark" : "light"
{
"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": "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
}
{
"id": "resetTheme",
"storaged": true,
"group": "aspect",
"component": "Button",
"title": "Reset theme",
"props": {
"children": "Default Theme"
},
"emitEvent": "resetTheme",
"noUpdate": true,
}
},
{
"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"
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,183 +2,191 @@ import React from "react"
import { User } from "models"
import loadable from "@loadable/component"
export default [
{
"id": "username",
"group": "account.basicInfo",
"component": "Button",
"icon": "AtSign",
"title": "Username",
"description": "Your username is the name you use to log in to your account.",
"props": {
"disabled": true,
"children": "Change username",
},
export default {
icon: "User",
label: "Profile",
ctxData: async () => {
const userData = await User.data()
return {
userData
}
},
{
"id": "fullName",
"group": "account.basicInfo",
"component": "Input",
"icon": "Edit3",
"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",
settings: [
{
"id": "username",
"group": "account.basicInfo",
"component": "Button",
"icon": "AtSign",
"title": "Username",
"description": "Your username is the name you use to log in to your account.",
"props": {
"disabled": true,
"children": "Change username",
},
},
"defaultValue": async () => {
const userData = await User.data()
return userData.fullName
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
{
"id": "fullName",
"group": "account.basicInfo",
"component": "Input",
"icon": "Edit3",
"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({
_id: selfId,
update: {
fullName: value
return ctx.userData.fullName
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId,
update: {
fullName: value
}
})
if (result) {
return result
}
})
if (result) {
return result
}
},
"extraActions": [
{
"id": "unset",
"icon": "Delete",
"title": "Unset",
"onClick": async () => {
window.app.api.withEndpoints("main").post.unsetPublicName()
},
"extraActions": [
{
"id": "unset",
"icon": "Delete",
"title": "Unset",
"onClick": async () => {
window.app.api.withEndpoints("main").post.unsetPublicName()
}
}
}
],
"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,
],
"debounced": true,
},
"defaultValue": async () => {
const userData = await User.data()
return userData.email
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
{
"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": (ctx) => {
return ctx.userData.email
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId,
update: {
email: value
const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId,
update: {
email: value
}
})
if (result) {
return result
}
})
if (result) {
return result
}
},
"debounced": true,
},
"debounced": true,
},
{
"id": "avatar",
"group": "account.profile",
"icon": "Image",
"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
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
{
"id": "avatar",
"group": "account.profile",
"icon": "Image",
"title": "Avatar",
"description": "Change your avatar (Upload an image or use an URL)",
"component": loadable(() => import("../components/ImageUploader")),
"defaultValue": (ctx) => {
return ctx.userData.avatar
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId,
update: {
avatar: value
const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId,
update: {
avatar: value
}
})
if (result) {
app.message.success("Avatar updated")
return result
}
})
if (result) {
app.message.success("Avatar updated")
return result
}
},
"debounced": true,
},
"debounced": true,
},
{
"id": "cover",
"group": "account.profile",
"icon": "Image",
"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
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
{
"id": "cover",
"group": "account.profile",
"icon": "Image",
"title": "Cover",
"description": "Change your profile cover (Upload an image or use an URL)",
"component": loadable(() => import("../components/ImageUploader")),
"defaultValue": (ctx) => {
return ctx.userData.cover
},
"onUpdate": async (value) => {
const selfId = await User.selfUserId()
const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId,
update: {
cover: value
const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId,
update: {
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) {
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": 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({
_id: selfId,
update: {
description: value
}
})
const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId,
update: {
description: value
if (result) {
return result
}
})
if (result) {
return result
}
},
"debounced": true,
},
"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
export default [
{
"id": "change-password",
"group": "security.account",
"title": "Change Password",
"description": "Change your password",
"icon": "Lock",
"component": loadable(() => import("../components/changePassword")),
},
{
"id": "two-factor-authentication",
"group": "security.account",
"title": "Two-Factor Authentication",
"description": "Add an extra layer of security to your account",
"icon": "MdOutlineSecurity",
"component": "Switch",
},
{
"id": "sessions",
"group": "security.account",
"title": "Sessions",
"description": "Manage your active sessions",
"icon": "Monitor",
"component": loadable(() => import("../components/sessions")),
"storaged": false
},
{
"id": "logout",
"group": "security.other",
"component": "Button",
"icon": "LogOut",
"title": "Logout",
"description": "Logout from your account",
"emitEvent": "session.logout",
}
]
export default {
icon: "Shield",
label: "Security",
settings: [
{
"id": "change-password",
"group": "security.account",
"title": "Change Password",
"description": "Change your password",
"icon": "Lock",
"component": loadable(() => import("../components/changePassword")),
},
{
"id": "two-factor-authentication",
"group": "security.account",
"title": "Two-Factor Authentication",
"description": "Add an extra layer of security to your account",
"icon": "MdOutlineSecurity",
"component": "Switch",
},
{
"id": "sessions",
"group": "security.account",
"title": "Sessions",
"description": "Manage your active sessions",
"icon": "Monitor",
"component": loadable(() => import("../components/sessions")),
"storaged": false
},
{
"id": "logout",
"group": "security.other",
"component": "Button",
"icon": "LogOut",
"title": "Logout",
"description": "Logout from your account",
"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,37 +302,140 @@ const SettingItem = (props) => {
</div>
<div className="component">
<div>
{loading ? <div> Loading... </div> : React.createElement(SettingComponent, {
...item.props,
ctx: {
currentValue: value,
dispatchUpdate,
onUpdateItem,
...props.ctx,
}
})}
{
loading
? <div> Loading... </div>
: React.createElement(SettingComponent, {
...item.props,
ctx: {
currentValue: value,
dispatchUpdate,
onUpdateItem,
...props.ctx,
}
})}
</div>
{delayedValue && <div>
<antd.Button
type="round"
icon={<Icons.Save />}
onClick={async () => await dispatchUpdate(value)}
>
Save
</antd.Button>
</div>}
{
delayedValue && <div>
<antd.Button
type="round"
icon={<Icons.Save />}
onClick={async () => await dispatchUpdate(value)}
>
Save
</antd.Button>
</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", {
["mobile"]: window.isMobile,
})
}>
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>