From ed51b91e3ddb9936dad2c471c0f8cc9fa303f8f8 Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Tue, 13 Jun 2023 22:41:46 +0000 Subject: [PATCH 1/4] added missings --- packages/app/constants/defaultSettings.json | 2 +- .../constants/settings/apparence/index.jsx | 1 + .../app/constants/settings/general/index.jsx | 224 +++++++++--------- .../app/constants/settings/player/index.jsx | 27 +++ .../app/constants/settings/profile/index.jsx | 11 +- .../app/constants/settings/security/index.jsx | 11 - 6 files changed, 143 insertions(+), 133 deletions(-) diff --git a/packages/app/constants/defaultSettings.json b/packages/app/constants/defaultSettings.json index f6900f6d..c29342f5 100755 --- a/packages/app/constants/defaultSettings.json +++ b/packages/app/constants/defaultSettings.json @@ -8,7 +8,7 @@ "longPressDelay": 600, "autoCollapseDelay": 500, "autoCollapseDelayEnabled": true, - "haptic_feedback": false, + "haptic_feedback": true, "collapseOnLooseFocus": true, "style.auto_darkMode": true, "feed_max_fetch": 20, diff --git a/packages/app/constants/settings/apparence/index.jsx b/packages/app/constants/settings/apparence/index.jsx index be5b2e3d..13978853 100755 --- a/packages/app/constants/settings/apparence/index.jsx +++ b/packages/app/constants/settings/apparence/index.jsx @@ -21,6 +21,7 @@ export default { group: "layout", emitEvent: "app.softReload", storaged: true, + mobile: false, }, { id: "style.reduceAnimations", diff --git a/packages/app/constants/settings/general/index.jsx b/packages/app/constants/settings/general/index.jsx index 57035c2a..84e25b0e 100755 --- a/packages/app/constants/settings/general/index.jsx +++ b/packages/app/constants/settings/general/index.jsx @@ -1,6 +1,4 @@ -import React from "react" import config from "config" -import { Select } from "antd" export default { id: "general", @@ -10,38 +8,42 @@ export default { order: 0, 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 {language.name} + id: "language", + storaged: true, + group: "general", + component: "Select", + icon: "MdTranslate", + title: "Language", + description: "Choose a language for the application", + props: { + options: config.i18n.languages.map((language) => { + return { + label: language.name, + value: language.locale + } }) }, - "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: "haptic_feedback", + storaged: true, + group: "general", + component: "Switch", + icon: "MdVibration", + title: "Haptic Feedback", + description: "Enable haptic feedback on touch events.", + desktop: false }, { - "id": "longPressDelay", - "storaged": true, - "group": "general", - "component": "Slider", - "icon": "MdTimer", - "title": "Long press delay", - "description": "Set the delay before long press trigger is activated.", - "props": { + id: "longPressDelay", + storaged: true, + group: "general", + component: "Slider", + icon: "MdTimer", + title: "Long press delay", + description: "Set the delay before long press trigger is activated.", + props: { min: 300, max: 2000, step: 100, @@ -55,24 +57,29 @@ export default { } }, { - "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: "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", + props: { + danger: true, + children: "Clear" + }, }, { - "id": "low_performance_mode", - "storaged": true, - "group": "general", - "component": "Switch", - "icon": "MdSlowMotionVideo", - "title": "Low performance mode", - "description": "Enable low performance mode to reduce the memory usage and improve the performance in low-end devices. This will disable some animations and other decorative features.", - "emitEvent": "app.lowPerformanceMode", + id: "low_performance_mode", + storaged: true, + group: "general", + component: "Switch", + icon: "MdSlowMotionVideo", + title: "Low performance mode", + description: "Enable low performance mode to reduce the memory usage and improve the performance in low-end devices. This will disable some animations and other decorative features.", + emitEvent: "app.lowPerformanceMode", + experimental: true, disabled: true, }, { @@ -83,6 +90,7 @@ export default { icon: "MdVolumeUp", title: "UI effects", description: "Enable the UI effects.", + mobile: false, }, { id: "ui.general_volume", @@ -100,64 +108,68 @@ export default { max: 100, step: 10, }, - emitEvent: "change:app.general_ui_volume" + emitEvent: "change:app.general_ui_volume", + mobile: false, }, { - "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_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_vibrate", + storaged: true, + group: "notifications", + component: "Switch", + icon: "MdVibration", + title: "Vibration", + description: "Vibrate the device when a notification is received.", + emitEvent: "changeNotificationsVibrate", + desktop: false, }, { - "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": { + 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" + emitEvent: "changeNotificationsSoundVolume", + mobile: false, }, { - "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: "collapseOnLooseFocus", + storaged: true, + group: "sidebar", + component: "Switch", + icon: "Columns", + title: "Auto Collapse", + description: "Collapse the sidebar when loose focus", + emitEvent: "settingChanged.sidebar_collapse", + mobile: false, }, { - "id": "autoCollapseDelay", - "storaged": true, - "group": "sidebar", - "component": "Slider", - "icon": "MdTimer", - "dependsOn": { + 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": { + title: "Auto Collapse timeout", + description: "Set the delay before the sidebar is collapsed", + props: { min: 0, max: 2000, step: 100, @@ -168,41 +180,21 @@ export default { 1500: "1.5s", 2000: "2s", } - } + }, + mobile: false, }, { - "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": { + 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" + storaged: true, }, ] } \ No newline at end of file diff --git a/packages/app/constants/settings/player/index.jsx b/packages/app/constants/settings/player/index.jsx index 80d23797..ea1c91f9 100755 --- a/packages/app/constants/settings/player/index.jsx +++ b/packages/app/constants/settings/player/index.jsx @@ -92,6 +92,33 @@ export default { "player.compressor": true }, storaged: false, + }, + { + id: "player.gain", + title: "Gain", + icon: "MdGraphicEq", + group: "general", + description: "Adjust gain for audio output", + component: "Slider", + props: { + min: 1, + max: 2, + step: 0.1, + marks: { + 1: "Off", + 1.5: "50%", + 2: "100%" + } + }, + defaultValue: () => { + return app.cores.player.gain.values().gain + }, + onUpdate: (value) => { + app.cores.player.gain.modifyValues({ + gain: value + }) + }, + storaged: false, } ] } \ No newline at end of file diff --git a/packages/app/constants/settings/profile/index.jsx b/packages/app/constants/settings/profile/index.jsx index 431eb826..0a996589 100755 --- a/packages/app/constants/settings/profile/index.jsx +++ b/packages/app/constants/settings/profile/index.jsx @@ -51,7 +51,7 @@ export default { }) if (result) { - return result + return value } }, extraActions: [ @@ -89,7 +89,7 @@ export default { }) if (result) { - return result + return value } }, "debounced": true, @@ -105,6 +105,7 @@ export default { UploadButton ], "defaultValue": (ctx) => { + console.log(ctx) return ctx.userData.avatar }, "onUpdate": async (value) => { @@ -114,7 +115,7 @@ export default { if (result) { app.message.success("Avatar updated") - return result + return value } }, "debounced": true, @@ -139,7 +140,7 @@ export default { if (result) { app.message.success("Cover updated") - return result + return value } }, "debounced": true, @@ -166,7 +167,7 @@ export default { }) if (result) { - return result + return value } }, "debounced": true, diff --git a/packages/app/constants/settings/security/index.jsx b/packages/app/constants/settings/security/index.jsx index 9be27e26..66e6e0d9 100755 --- a/packages/app/constants/settings/security/index.jsx +++ b/packages/app/constants/settings/security/index.jsx @@ -34,17 +34,6 @@ export default { "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", - onUpdate: async () => { - await AuthModel.logout() - } } ] } \ No newline at end of file From aa3e6dc53c738d997af0a3e92a415992b00c1037 Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Tue, 13 Jun 2023 22:42:01 +0000 Subject: [PATCH 2/4] refactor new method to parse settings --- packages/app/constants/settings/index.js | 137 +++++++++++++++++++++-- 1 file changed, 125 insertions(+), 12 deletions(-) diff --git a/packages/app/constants/settings/index.js b/packages/app/constants/settings/index.js index 10395dda..4789f170 100755 --- a/packages/app/constants/settings/index.js +++ b/packages/app/constants/settings/index.js @@ -1,26 +1,139 @@ -const settingsPaths = import.meta.glob("/constants/settings/*/index.jsx") +async function composeSettingsByGroups() { + console.time("load settings") -export default async () => { - const settings = {} + /* @vite-ignore */ + let _settings = import.meta.glob("/constants/settings/*/index.jsx") - for (const [key, value] of Object.entries(settingsPaths)) { - const path = key.split("/").slice(-2) + _settings = Object.entries(_settings).map(([route, moduleFile]) => { + const path = route.split("/").slice(-2) const name = path[0] if (name === "components" || name === "index") { - continue + return null } - if (!settings[name]) { - settings[name] = {} + return moduleFile + }) + + _settings = _settings.filter((moduleFile) => moduleFile) + + _settings = await Promise.all(_settings.map((moduleFile) => moduleFile())) + + _settings = _settings.map((moduleFile) => { + return moduleFile.default || moduleFile + }) + + _settings = _settings.sort((a, b) => { + if (a.group === "bottom") { + return 1 } - let setting = await value() + if (b.group === "bottom") { + return -1 + } - setting = setting.default || setting + return 0 + }) - settings[name] = setting + _settings = _settings.reduce((acc, settingModule) => { + if (typeof acc[settingModule.group] !== "object") { + acc[settingModule.group] = [] + } + + acc[settingModule.group].push(settingModule) + + return acc + }, {}) + + _settings = Object.entries(_settings).map(([group, groupModule]) => { + // filter setting by platform + groupModule = groupModule.map((subGroup) => { + if (Array.isArray(subGroup.settings)) { + subGroup.settings = subGroup.settings.filter((setting) => { + if (!app.isMobile && setting.desktop === false) { + return false + } + + if (app.isMobile && setting.mobile === false) { + return false + } + + return true + }) + } + + return subGroup + }) + + return { + group, + groupModule: groupModule + } + }) + + // order groups + _settings = _settings.map((group) => { + group.groupModule = group.groupModule.sort((a, b) => { + if (typeof a.order === undefined) { + // find index + a.order = group.groupModule.indexOf(a) + } + + if (typeof b.order === undefined) { + // find index + b.order = group.groupModule.indexOf(b) + } + + return a.order - b.order + }) + + return group + }) + + console.timeEnd("load settings") + + return _settings +} + +function composeTabsFromGroups(settingsGroups) { + return settingsGroups.reduce((acc, entry) => { + entry.groupModule.forEach((item) => { + if (item.id) { + acc[item.id] = item + } + }) + + return acc + }, {}) +} + +function composeGroupsFromSettingsTab(settings) { + if (!Array.isArray(settings)) { + console.error("settings is not an array") + return [] } - return settings + return settings.reduce((acc, setting) => { + if (setting.group) { + if (typeof acc[setting.group] === "undefined") { + acc[setting.group] = [] + } + + acc[setting.group].push(setting) + } + + return acc + }, {}) +} + +const composedSettingsByGroups = await composeSettingsByGroups() +const composedTabs = composeTabsFromGroups(composedSettingsByGroups) + +export { + composedSettingsByGroups, + composedTabs, + + composeSettingsByGroups, + composeTabsFromGroups, + composeGroupsFromSettingsTab, } \ No newline at end of file From 7069073d06e451b0f47e4f0cee37f49a3e550f8b Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Tue, 13 Jun 2023 22:42:18 +0000 Subject: [PATCH 3/4] split components --- .../components/SettingItemComponent/index.jsx | 517 ++++++++++++++++ .../settings/components/SettingTab/index.jsx | 117 ++++ packages/app/src/pages/settings/index.jsx | 564 ++---------------- packages/app/src/pages/settings/index.less | 93 +-- .../app/src/pages/settings/index.mobile.jsx | 135 +++++ .../app/src/pages/settings/index.mobile.less | 156 +++++ 6 files changed, 1022 insertions(+), 560 deletions(-) create mode 100644 packages/app/src/pages/settings/components/SettingItemComponent/index.jsx create mode 100644 packages/app/src/pages/settings/components/SettingTab/index.jsx mode change 100755 => 100644 packages/app/src/pages/settings/index.jsx create mode 100644 packages/app/src/pages/settings/index.mobile.jsx create mode 100644 packages/app/src/pages/settings/index.mobile.less diff --git a/packages/app/src/pages/settings/components/SettingItemComponent/index.jsx b/packages/app/src/pages/settings/components/SettingItemComponent/index.jsx new file mode 100644 index 00000000..db5f8ed6 --- /dev/null +++ b/packages/app/src/pages/settings/components/SettingItemComponent/index.jsx @@ -0,0 +1,517 @@ +import React from "react" +import * as antd from "antd" + +import { Translation } from "react-i18next" +import { SliderPicker } from "react-color" + +import { Icons, createIconRender } from "components/Icons" + +class PerformanceLog { + constructor( + id, + params = { + disabled: false + } + ) { + this.id = id + this.params = params + + this.table = {} + + return this + } + + start(event) { + if (this.params.disabled) { + return false + } + + if (!this.table[event]) { + this.table[event] = {} + } + + return this.table[event]["start"] = performance.now() + } + + end(event) { + if (this.params.disabled) { + return false + } + + if (!this.table[event]) { + return + } + + return this.table[event]["end"] = performance.now() + } + + finally() { + if (this.params.disabled) { + return false + } + + console.group(this.id) + + Object.entries(this.table).forEach(([entry, value]) => { + console.log(entry, `${(value.end - value.start).toFixed(0)}ms`) + }) + + console.groupEnd() + } +} + +export const SettingsComponents = { + button: { + component: antd.Button, + props: (_this) => { + return { + onClick: (event) => _this.onUpdateItem(event) + } + } + }, + switch: { + component: antd.Switch, + props: (_this) => { + return { + onChange: (event) => _this.onUpdateItem(event) + } + } + }, + slider: { + component: antd.Slider, + props: (_this) => { + return { + onAfterChange: (event) => _this.onUpdateItem(event) + } + } + }, + input: { + component: antd.Input, + props: (_this) => { + return { + defaultValue: _this.state.value, + onChange: (event) => _this.onUpdateItem(event.target.value), + onPressEnter: (event) => _this.dispatchUpdate(event.target.value) + } + } + }, + textarea: { + component: antd.Input.TextArea, + props: (_this) => { + return { + defaultValue: _this.state.value, + onChange: (event) => _this.onUpdateItem(event.target.value), + onPressEnter: (event) => _this.dispatchUpdate(event.target.value) + } + } + }, + inputnumber: { + component: antd.InputNumber, + }, + select: { + component: antd.Select, + props: (_this) => { + return { + onChange: (event) => { + console.log(event) + _this.onUpdateItem(event) + } + } + } + }, + slidercolorpicker: { + component: SliderPicker, + props: (_this) => { + return { + onChange: (color) => { + _this.setState({ + componentProps: { + ..._this.state.componentProps, + color + } + }) + }, + onChangeComplete: (color) => { + _this.onUpdateItem(color.hex) + }, + color: _this.state.value + } + } + }, +} + +export default class SettingItemComponent extends React.PureComponent { + state = { + value: null, + debouncedValue: null, + + componentProps: Object(), + loading: true, + } + + perf = new PerformanceLog(`Init ${this.props.setting.id}`, { + disabled: true + }) + + componentType = null + + componentRef = React.createRef() + + componentDidMount = async () => { + if (typeof this.props.setting.component === "string") { + this.componentType = String(this.props.setting.component).toLowerCase() + } + + await this.initialize() + } + + componentWillUnmount = () => { + this.setState({ + value: null, + componentProps: Object(), + }) + + if (typeof this.props.setting.dependsOn === "object") { + for (const key in this.props.setting.dependsOn) { + window.app.eventBus.off(`setting.update.${key}`) + } + } + } + + generateInhertedProps = () => { + if (!SettingsComponents[this.componentType]) { + return {} + } + + if (typeof SettingsComponents[this.componentType].props === "function") { + const inhertedProps = SettingsComponents[this.componentType].props(this) + + return inhertedProps + } + + return {} + } + + toogleLoading = (to) => { + if (typeof to === "undefined") { + to = !this.state.loading + } + + this.setState({ + loading: to + }) + } + + initialize = async () => { + this.perf.start(`init tooks`) + + this.toogleLoading(true) + + if (this.props.setting.storaged) { + this.perf.start(`get value from storaged`) + + await this.setState({ + value: window.app.cores.settings.get(this.props.setting.id), + }) + + this.perf.end(`get value from storaged`) + } + + if (typeof this.props.setting.defaultValue === "function") { + this.perf.start(`execute default value fn`) + + this.toogleLoading(true) + + this.setState({ + value: await this.props.setting.defaultValue(this.props.ctx) + }) + + this.toogleLoading(false) + + this.perf.end(`execute default value fn`) + } + + if (typeof this.props.setting.dependsOn === "object") { + this.perf.start(`register dependsOn events`) + + Object.keys(this.props.setting.dependsOn).forEach((key) => { + // create a event handler to watch changes + window.app.eventBus.on(`setting.update.${key}`, () => { + this.setState({ + componentProps: { + ...this.state.componentProps, + disabled: this.checkDependsValidation() + } + }) + }) + }) + + this.perf.end(`register dependsOn events`) + + this.perf.start(`check depends validation`) + + // by default check depends validation + this.setState({ + componentProps: { + ...this.state.componentProps, + disabled: this.checkDependsValidation() + } + }) + + this.perf.end(`check depends validation`) + } + + if (typeof this.props.setting.listenUpdateValue === "string") { + this.perf.start(`listen "on update" value`) + + window.app.eventBus.on(`setting.update.${this.props.setting.listenUpdateValue}`, () => { + this.setState({ + value: window.app.cores.settings.get(this.props.setting.id) + }) + }) + + this.perf.end(`listen "on update" value`) + } + + if (this.props.setting.reloadValueOnUpdateEvent) { + this.perf.start(`Reinitializing setting [${this.props.setting.id}]`) + + window.app.eventBus.on(this.props.setting.reloadValueOnUpdateEvent, () => { + console.log(`Reinitializing setting [${this.props.setting.id}]`) + this.initialize() + }) + + this.perf.end(`Reinitializing setting [${this.props.setting.id}]`) + } + + this.toogleLoading(false) + + this.perf.end(`init tooks`) + + this.perf.finally() + } + + dispatchUpdate = async (updateValue) => { + if (typeof this.props.setting.onUpdate === "function") { + try { + const result = await this.props.setting.onUpdate(updateValue) + + if (result) { + updateValue = result + } + } catch (error) { + console.error(error) + + if (error.response.data.error) { + app.message.error(error.response.data.error) + } else { + app.message.error(error.message) + } + + return false + } + } + + const storagedValue = window.app.cores.settings.get(this.props.setting.id) + + if (typeof updateValue === "undefined") { + updateValue = !storagedValue + } + + if (this.props.setting.storaged) { + await window.app.cores.settings.set(this.props.setting.id, updateValue) + + if (typeof this.props.setting.beforeSave === "function") { + await this.props.setting.beforeSave(updateValue) + } + } + + if (typeof this.props.setting.emitEvent !== "undefined") { + if (typeof this.props.setting.emitEvent === "string") { + this.props.setting.emitEvent = [this.props.setting.emitEvent] + } + + let emissionPayload = updateValue + + if (typeof this.props.setting.emissionValueUpdate === "function") { + emissionPayload = this.props.setting.emissionValueUpdate(emissionPayload) + } + + for await (const event of this.props.setting.emitEvent) { + window.app.eventBus.emit(event, emissionPayload) + } + } + + if (this.props.setting.noUpdate) { + return false + } + + // reset debounced value + if (this.props.setting.debounced) { + await this.setState({ + debouncedValue: null + }) + } + + if (this.componentRef.current) { + if (typeof this.componentRef.current.onDebounceSave === "function") { + await this.componentRef.current.onDebounceSave(updateValue) + } + } + + // finaly update value + await this.setState({ + value: updateValue + }) + + return updateValue + } + + onUpdateItem = async (updateValue) => { + this.setState({ + value: updateValue + }) + + if (this.props.setting.debounced) { + return await this.setState({ + debouncedValue: updateValue + }) + } + + return await this.dispatchUpdate(updateValue) + } + + checkDependsValidation = () => { + return !Boolean(Object.keys(this.props.setting.dependsOn).every((key) => { + const storagedValue = window.app.cores.settings.get(key) + + console.debug(`Checking validation for [${key}] with now value [${storagedValue}]`) + + if (typeof this.props.setting.dependsOn[key] === "function") { + return this.props.setting.dependsOn[key](storagedValue) + } + + return storagedValue === this.props.setting.dependsOn[key] + })) + } + + render() { + if (!this.props.setting) { + console.error(`Item [${this.props.setting.id}] has no an setting!`) + return null + } + + if (!this.props.setting.component) { + console.error(`Item [${this.props.setting.id}] has no an setting component!`) + return null + } + + let finalProps = { + ...this.state.componentProps, + ...this.props.setting.props, + + ctx: { + updateCurrentValue: (updateValue) => this.setState({ + value: updateValue + }), + getCurrentValue: () => this.state.value, + currentValue: this.state.value, + dispatchUpdate: this.dispatchUpdate, + onUpdateItem: this.onUpdateItem, + }, + ref: this.componentRef, + + ...this.generateInhertedProps(), + + // set values + checked: this.state.value, + value: this.state.value, + + size: app.isMobile ? "large" : "default" + } + + if (this.props.setting.children) { + finalProps.children = this.props.setting.children + } + + if (app.isMobile) { + finalProps.size = "large" + } + + const Component = SettingsComponents[String(this.props.setting.component).toLowerCase()]?.component ?? this.props.setting.component + + return
+
+
+
+

+ { + createIconRender(this.props.setting.icon) + } + + {(t) => t(this.props.setting.title ?? this.props.setting.id)} + +

+ {this.props.setting.experimental && Experimental } +
+
+

+ + {(t) => t(this.props.setting.description)} + +

+
+
+ + { + this.props.setting.extraActions &&
+ { + this.props.setting.extraActions.map((action, index) => { + if (typeof action === "function") { + return React.createElement(action) + } + + const handleOnClick = () => { + if (action.onClick) { + action.onClick(finalProps.ctx) + } + } + + return + {action.title} + + }) + } +
+ } +
+ +
+ <> + { + !this.state.loading && React.createElement(Component, finalProps) + } + { + this.state.loading && + } + { + this.state.debouncedValue && } + onClick={async () => await this.dispatchUpdate(this.state.debouncedValue)} + > + Save + + } + +
+
+ } +} \ No newline at end of file diff --git a/packages/app/src/pages/settings/components/SettingTab/index.jsx b/packages/app/src/pages/settings/components/SettingTab/index.jsx new file mode 100644 index 00000000..96067104 --- /dev/null +++ b/packages/app/src/pages/settings/components/SettingTab/index.jsx @@ -0,0 +1,117 @@ +import React from "react" +import * as antd from "antd" +import { Translation } from "react-i18next" + +import { Icons } from "components/Icons" + +import { + composedTabs, + composeGroupsFromSettingsTab, +} from "schemas/settings" + +import groupsDecorators from "schemas/settingsGroupsDecorators" + +import SettingItemComponent from "../SettingItemComponent" + +export default class SettingTab extends React.Component { + state = { + loading: true, + processedCtx: {} + } + + tab = composedTabs[this.props.activeKey] + + processCtx = async () => { + if (typeof this.tab.ctxData === "function") { + this.setState({ loading: true }) + + const resultCtx = await this.tab.ctxData() + + console.log(resultCtx) + + this.setState({ + loading: false, + processedCtx: resultCtx + }) + } + } + + // check if props.activeKey change + componentDidUpdate = async (prevProps) => { + if (prevProps.activeKey !== this.props.activeKey) { + this.tab = composedTabs[this.props.activeKey] + + this.setState({ + loading: !!this.tab.ctxData, + processedCtx: {} + }) + + await this.processCtx() + } + } + + componentDidMount = async () => { + this.setState({ + loading: !!this.tab.ctxData, + }) + + await this.processCtx() + + this.setState({ + loading: false + }) + } + + render() { + if (this.state.loading) { + return + } + + if (this.tab.render) { + return React.createElement(this.tab.render, { + ctx: this.state.processedCtx + }) + } + + if (this.props.withGroups) { + const group = composeGroupsFromSettingsTab(this.tab.settings) + + return Object.entries(group).map(([groupKey, settings], index) => { + const fromDecoratorIcon = groupsDecorators[groupKey]?.icon + const fromDecoratorTitle = groupsDecorators[groupKey]?.title + + return
+
+

+ { + fromDecoratorIcon ? React.createElement(Icons[fromDecoratorIcon]) : null + } + + { + t => t(fromDecoratorTitle ?? groupKey) + } + +

+
+ +
+ { + settings.map((setting) => ) + } +
+
+ }) + } + + return this.tab.settings.map((setting, index) => { + return + }) + } +} \ No newline at end of file diff --git a/packages/app/src/pages/settings/index.jsx b/packages/app/src/pages/settings/index.jsx old mode 100755 new mode 100644 index 3f216d92..c56e7ff6 --- a/packages/app/src/pages/settings/index.jsx +++ b/packages/app/src/pages/settings/index.jsx @@ -6,34 +6,35 @@ import classnames from "classnames" import config from "config" import useUrlQueryActiveKey from "hooks/useUrlQueryActiveKey" -import AuthModel from "models/auth" - import { Icons, createIconRender } from "components/Icons" -import getSettingsList from "schemas/settings" +import { + composedSettingsByGroups as settings +} from "schemas/settings" + import menuGroupsDecorators from "schemas/settingsMenuGroupsDecorators" -import groupsDecorators from "schemas/settingsGroupsDecorators" + +import SettingTab from "./components/SettingTab" import "./index.less" -const SettingsList = await getSettingsList() - const extraMenuItems = [ { - id: "donate", - label: "Support us", - icon: "Heart", - props: { - style: { - color: "#f72585" - } - } + key: "donate", + label:
+ {createIconRender("Heart")} + Support us +
, }, { - id: "logout", - label: "Logout", - icon: "MdOutlineLogout", - danger: true + key: "logout", + label:
+ {createIconRender("MdOutlineLogout")} + Logout +
, + danger: true, } ] @@ -44,493 +45,16 @@ const menuEvents = { } }, "logout": () => { - antd.Modal.confirm({ - title: "Logout", - content: "Are you sure you want to logout?", - onOk: () => { - AuthModel.logout() - }, - }) + app.eventBus.emit("app.logout_request") } } -const ItemTypes = { - Button: antd.Button, - Switch: antd.Switch, - Slider: antd.Slider, - Checkbox: antd.Checkbox, - Input: antd.Input, - TextArea: antd.Input.TextArea, - InputNumber: antd.InputNumber, - Select: antd.Select, - SliderColorPicker: SliderPicker, -} - -const SettingItem = (props) => { - let { item } = props - - const [loading, setLoading] = React.useState(true) - const [value, setValue] = React.useState(null) - const [delayedValue, setDelayedValue] = React.useState(null) - const [disabled, setDisabled] = React.useState(false) - const componentRef = React.useRef(null) - - let SettingComponent = item.component - - if (!SettingComponent) { - console.error(`Item [${item.id}] has no an component!`) - return null - } - - if (typeof item.props === "undefined") { - item.props = {} - } - - const dispatchUpdate = async (updateValue) => { - if (typeof item.onUpdate === "function") { - try { - const result = await item.onUpdate(updateValue) - - if (result) { - updateValue = result - } - } catch (error) { - console.error(error) - - if (error.response.data.error) { - app.message.error(error.response.data.error) - } else { - app.message.error(error.message) - } - - return false - } - } else { - const storagedValue = await window.app.cores.settings.get(item.id) - - if (typeof updateValue === "undefined") { - updateValue = !storagedValue - } - } - - if (item.storaged) { - await window.app.cores.settings.set(item.id, updateValue) - } - - if (item.storaged && typeof item.beforeSave === "function") { - await item.beforeSave(updateValue) - } - - if (typeof item.emitEvent !== "undefined") { - let emissionPayload = updateValue - - if (typeof item.emissionValueUpdate === "function") { - emissionPayload = item.emissionValueUpdate(emissionPayload) - } - - if (Array.isArray(item.emitEvent)) { - window.app.eventBus.emit(...item.emitEvent, emissionPayload) - } else if (typeof item.emitEvent === "string") { - window.app.eventBus.emit(item.emitEvent, emissionPayload) - } - } - - if (item.noUpdate) { - return false - } - - if (item.debounced) { - setDelayedValue(null) - } - - if (componentRef.current) { - if (typeof componentRef.current.onDebounceSave === "function") { - await componentRef.current.onDebounceSave(updateValue) - } - } - - setValue(updateValue) - } - - const onUpdateItem = async (updateValue) => { - setValue(updateValue) - - if (!item.debounced) { - await dispatchUpdate(updateValue) - } else { - setDelayedValue(updateValue) - } - } - - const checkDependsValidation = () => { - return !Boolean(Object.keys(item.dependsOn).every((key) => { - const storagedValue = window.app.cores.settings.get(key) - - console.debug(`Checking validation for [${key}] with now value [${storagedValue}]`) - - if (typeof item.dependsOn[key] === "function") { - return item.dependsOn[key](storagedValue) - } - - return storagedValue === item.dependsOn[key] - })) - } - - const settingInitialization = async () => { - if (item.storaged) { - const storagedValue = window.app.cores.settings.get(item.id) - setValue(storagedValue) - } - - if (typeof item.defaultValue === "function") { - setLoading(true) - - setValue(await item.defaultValue(props.ctx)) - - setLoading(false) - } - - if (item.disabled === true) { - setDisabled(true) - } - - if (typeof item.dependsOn === "object") { - // create a event handler to watch changes - Object.keys(item.dependsOn).forEach((key) => { - window.app.eventBus.on(`setting.update.${key}`, () => { - setDisabled(checkDependsValidation()) - }) - }) - - // by default check depends validation - setDisabled(checkDependsValidation()) - } - - if (typeof item.listenUpdateValue === "string") { - window.app.eventBus.on(`setting.update.${item.listenUpdateValue}`, (value) => setValue(value)) - } - - if (item.reloadValueOnUpdateEvent) { - window.app.eventBus.on(item.reloadValueOnUpdateEvent, () => { - console.log(`Reloading value for item [${item.id}]`) - settingInitialization() - }) - } - - setLoading(false) - } - - React.useEffect(() => { - settingInitialization() - - 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 ItemTypes[SettingComponent] === "undefined") { - console.error(`Item [${item.id}] has an invalid component: ${item.component}`) - return null - } - - switch (SettingComponent.toLowerCase()) { - case "slidercolorpicker": { - item.props.onChange = (color) => { - item.props.color = color.hex - } - item.props.onChangeComplete = (color) => { - onUpdateItem(color.hex) - } - - item.props.color = value - - break - } - case "textarea": { - item.props.defaultValue = value - item.props.onPressEnter = (event) => dispatchUpdate(event.target.value) - item.props.onChange = (event) => onUpdateItem(event.target.value) - break - } - case "input": { - item.props.defaultValue = value - item.props.onPressEnter = (event) => dispatchUpdate(event.target.value) - item.props.onChange = (event) => onUpdateItem(event.target.value) - break - } - case "switch": { - item.props.checked = value - item.props.onClick = (event) => onUpdateItem(event) - break - } - case "select": { - item.props.onChange = (value) => onUpdateItem(value) - item.props.defaultValue = value - break - } - case "slider": { - item.props.defaultValue = value - item.props.onAfterChange = (value) => onUpdateItem(value) - break - } - default: { - if (!item.props.children) { - item.props.children = item.title ?? item.id - } - - item.props.value = item.defaultValue - item.props.onClick = (event) => onUpdateItem(event) - - break - } - } - - // override with default item component - SettingComponent = ItemTypes[SettingComponent] - } - - item.props["disabled"] = disabled - - const elementsCtx = { - updateCurrentValue: (value) => setValue(value), - currentValue: value, - dispatchUpdate, - onUpdateItem, - ...props.ctx, - } - - return
-
-
-
-

- {Icons[item.icon] ? React.createElement(Icons[item.icon]) : null} - { - t => t(item.title ?? item.id) - } -

-

{ - t => t(item.description) - }

-
-
- {item.experimental && Experimental } -
-
- {item.extraActions && -
- {item.extraActions.map((action, index) => { - if (typeof action === "function") { - return React.createElement(action, { - ctx: elementsCtx, - }) - } - - const handleOnClick = () => { - if (action.onClick) { - action.onClick(elementsCtx) - } - } - - return - {action.title} - - })} -
- } -
-
-
- { - loading - ?
Loading...
- : React.createElement(SettingComponent, { - ...item.props, - ctx: elementsCtx, - ref: componentRef, - })} -
- - { - delayedValue &&
- } - onClick={async () => await dispatchUpdate(value)} - > - Save - -
- } -
-
-} - -const SettingGroup = React.memo((props) => { - const { - ctx, - groupKey, - settings, - loading, - disabled - } = props - - const fromDecoratorIcon = groupsDecorators[groupKey]?.icon - const fromDecoratorTitle = groupsDecorators[groupKey]?.title - - if (loading) { - return - } - - if (disabled) { - return null - } - - return
-
-

- { - fromDecoratorIcon ? React.createElement(Icons[fromDecoratorIcon]) : null - } - - { - t => t(fromDecoratorTitle ?? groupKey) - } - -

-
-
- { - settings.map((item) => ) - } -
-
-}) - -const SettingTab = (props) => { - const [groups, setGroups] = React.useState({}) - const [loading, setLoading] = React.useState(true) - const [ctxData, setCtxData] = React.useState({}) - - const processCtx = async () => { - setLoading(true) - - if (typeof props.tab.ctxData === "function") { - const resultCtx = await props.tab.ctxData() - - setCtxData(resultCtx) - } - - setLoading(false) - } - - React.useEffect(() => { - if (!Array.isArray(props.tab.settings)) { - console.error("Cannot generate settings from non-array") - - return [] - } - - let groupsSettings = {} - - props.tab.settings.forEach((item) => { - if (!groupsSettings[item.group]) { - groupsSettings[item.group] = [] - } - - groupsSettings[item.group].push(item) - }) - - setGroups(groupsSettings) - - processCtx() - }, [props.tab]) - - if (loading) { - return - } - - return Object.keys(groups).map((groupKey) => { - return - }) -} - const generateMenuItems = () => { - const groups = {} - - Object.keys(SettingsList).forEach((tabKey) => { - const tab = SettingsList[tabKey] - - if (!tab.group) { - tab.group = "Others" - } - - groups[tab.group] = groups[tab.group] ?? [] - - groups[tab.group].push(tab) - }) - - if (typeof groups["bottom"] === undefined) { - groups["bottom"] = [] - } - - // add extra menu items - extraMenuItems.forEach((item) => { - groups["bottom"].push(item) - }) - - let groupsKeys = Object.keys(groups) - - // make "bottom" group last - groupsKeys = groupsKeys.sort((a, b) => { - if (a === "bottom") { - return 1 - } - - if (b === "bottom") { - return -1 - } - - return 0 - }) - - return groupsKeys.map((groupKey, index) => { - const ordererItems = groups[groupKey].sort((a, b) => { - if (typeof a.order === "undefined") { - a.order = groups[groupKey].indexOf(a) - } - - if (typeof b.order === "undefined") { - b.order = groups[groupKey].indexOf(b) - } - - // if value is close to 0, more to the top - return a.order - b.order - }) - - const children = ordererItems.map((item) => { + return settings.map((entry, index) => { + const children = entry.groupModule.map((item) => { return { key: item.id, + type: "item", label:
{createIconRender(item.icon ?? "Settings")} {item.label} @@ -541,38 +65,37 @@ const generateMenuItems = () => { } }) - if (index !== groupsKeys.length - 1) { + if (index !== settings.length - 1) { children.push({ type: "divider", }) } return { - key: groupKey, - label: groupKey === "bottom" ? null : <> + key: entry.group, + type: "group", + children: children, + label: entry.group === "bottom" ? null : <> { - menuGroupsDecorators[groupKey]?.icon && createIconRender(menuGroupsDecorators[groupKey]?.icon ?? "Settings") + menuGroupsDecorators[entry.group]?.icon && createIconRender(menuGroupsDecorators[groupKey]?.icon ?? "Settings") } { - t => t(menuGroupsDecorators[groupKey]?.label ?? groupKey) + t => t(menuGroupsDecorators[entry.group]?.label ?? entry.group) } - , - type: "group", - children: children, + } }) } + export default () => { const [activeKey, setActiveKey] = useUrlQueryActiveKey({ defaultKey: "general", queryKey: "tab" }) - const [menuItems, setMenuItems] = React.useState([]) - const onChangeTab = (event) => { if (typeof menuEvents[event.key] === "function") { return menuEvents[event.key]() @@ -583,16 +106,19 @@ export default () => { setActiveKey(event.key) } - React.useEffect(() => { - setMenuItems(generateMenuItems()) + const menuItems = React.useMemo(() => { + const items = generateMenuItems() + + extraMenuItems.forEach((item) => { + items[settings.length - 1].children.push(item) + }) + + return items }, []) return
@@ -605,12 +131,10 @@ export default () => {
- { - SettingsList[activeKey] && - React.createElement(SettingsList[activeKey].render ?? SettingTab, { - tab: SettingsList[activeKey], - }) - } +
} \ No newline at end of file diff --git a/packages/app/src/pages/settings/index.less b/packages/app/src/pages/settings/index.less index 8c1eed05..52037fe5 100755 --- a/packages/app/src/pages/settings/index.less +++ b/packages/app/src/pages/settings/index.less @@ -6,6 +6,8 @@ justify-content: center; + width: 100%; + padding-bottom: 20px; .settings_menu { @@ -21,8 +23,8 @@ align-items: center; - width: 30%; - max-width: 300px; + width: 250px; + min-width: 250px; padding: 0 30px; @@ -41,8 +43,7 @@ display: flex; flex-direction: column; - width: 100%; - max-width: 700px; + width: 700px; gap: 20px; @@ -62,13 +63,10 @@ gap: 20px; .settings_content_group_header { - position: absolute; + position: relative; width: 100%; - top: 0; - left: 0; - h1, h2, h3, @@ -78,65 +76,68 @@ margin: 0; color: var(--background-color-contrast); } - - //-webkit-box-shadow: @card-shadow; - //-moz-box-shadow: @card-shadow; - //box-shadow: @card-shadow; - - padding: 20px; - - border-radius: 12px; } - .settings_content_group_settings { + .settings_list { display: flex; flex-direction: column; - margin-top: 50px; - gap: 30px; - .settings_content_group_item { + width: 100%; + + .setting_item { display: flex; flex-direction: column; + width: 100%; + padding: 0 20px; - .settings_content_group_item_header { + .setting_item_header { display: inline-flex; flex-direction: row; + align-items: center; justify-content: space-between; width: 100%; - .settings_content_group_item_header_title { + .setting_item_info { display: flex; - align-items: center; - color: var(--background-color-contrast); + flex-direction: column; + + width: 100%; + + .setting_item_header_title { + display: flex; + flex-direction: row; + + align-items: center; + justify-content: space-between; + + width: 100%; - h1, - h2, - h3, - h4, - h5, - h6 { - margin: 0; color: var(--background-color-contrast); + + h1 { + font-size: 1rem; + margin: 0; + color: var(--background-color-contrast); + } } - p { - font-size: 11px; - color: var(--background-color-contrast); - margin: 0; + .setting_item_header_description { + p { + color: var(--background-color-contrast); + font-size: 0.7rem; + margin: 0; + } } - >div { - margin-right: 10px; - } } - .settings_content_group_item_header_actions { + .setting_item_header_actions { display: inline-flex; align-items: center; @@ -155,16 +156,28 @@ } } - .settings_content_group_item_component { + .setting_item_content { display: flex; flex-direction: column; --ignore-dragger: true; padding: 6px 20px; + width: 100%; + span { color: var(--background-color-contrast); } + + button { + width: min-content; + } + + .ant-btn.ant-btn-icon-only { + width: 32px; + } + + .ant-select {} } } } diff --git a/packages/app/src/pages/settings/index.mobile.jsx b/packages/app/src/pages/settings/index.mobile.jsx new file mode 100644 index 00000000..b6f99c2b --- /dev/null +++ b/packages/app/src/pages/settings/index.mobile.jsx @@ -0,0 +1,135 @@ +import React from "react" +import * as antd from "antd" + +import { Translation } from "react-i18next" +import useUrlQueryActiveKey from "hooks/useUrlQueryActiveKey" + +import { Icons, createIconRender } from "components/Icons" + +import { + composedSettingsByGroups as settingsGroups, + composedTabs, +} from "schemas/settings" + +import menuGroupsDecorators from "schemas/settingsMenuGroupsDecorators" +import SettingTab from "./components/SettingTab" + +import "./index.mobile.less" + +const SettingsHeader = ({ + activeKey, + back = () => { } +} = {}) => { + if (activeKey) { + const currentTab = composedTabs[activeKey] + + return
+ } + onClick={back} + size="large" + type="ghost" + /> + +

+ { + createIconRender(currentTab?.icon) + } + + {(t) => t(currentTab?.label ?? activeKey)} + +

+
+ } + + return
+

+ { + createIconRender("Settings") + } + + {(t) => t("Settings")} + +

+
+} + +export default (props) => { + let lastKey = null + + const [activeKey, setActiveKey] = useUrlQueryActiveKey({ + queryKey: "tab", + defaultKey: null, + }) + + const handleTabChange = (key) => { + // star page transition using new chrome transition api + if (document.startViewTransition) { + return document.startViewTransition(() => { + changeTab(key) + }) + } + + return changeTab(key) + } + + const goBack = () => { + handleTabChange(lastKey) + } + + const changeTab = (key) => { + lastKey = key + setActiveKey(key) + } + + return
+ + +
+ { + !activeKey && settingsGroups.map((entry) => { + const groupDecorator = menuGroupsDecorators[entry.group] + + return
+ + + {(t) => t(groupDecorator?.label ?? entry.group)} + + + +
+ { + entry.groupModule.map((settingsModule, index) => { + return { + handleTabChange(settingsModule.id) + }} + > + + {(t) => t(settingsModule.label)} + + + }) + } +
+
+ }) + } + + { + activeKey &&
+ +
+ } +
+
+} diff --git a/packages/app/src/pages/settings/index.mobile.less b/packages/app/src/pages/settings/index.mobile.less new file mode 100644 index 00000000..4f7a2e94 --- /dev/null +++ b/packages/app/src/pages/settings/index.mobile.less @@ -0,0 +1,156 @@ +@top_nav_height: 52px; + +.__mobile__settings { + display: flex; + flex-direction: column; + color: var(--text-color); + + gap: 30px; + + .__mobile__settings_header { + position: sticky; + top: 0; + left: 0; + z-index: 200; + + width: 100%; + height: @top_nav_height; + + display: flex; + flex-direction: row; + align-items: center; + + gap: 20px; + + padding: 5px 20px; + + border-radius: 12px; + + border-bottom: 1px solid var(--border-color); + background-color: var(--background-color-accent); + + h1 { + margin: 0; + view-transition-name: main-header-text; + width: fit-content; + } + + svg { + color: var(--colorPrimary); + } + + .ant-btn { + font-size: 2rem; + + svg { + color: var(--text-color); + } + } + } + + .settings_list { + view-transition-name: settings-list; + + display: flex; + flex-direction: column; + + color: var(--text-color); + + gap: 20px; + + .settings_list_group { + display: flex; + flex-direction: column; + + span { + font-size: 0.8rem; + font-weight: 600; + + margin: 0; + } + + .settings_list_group_items { + display: flex; + flex-direction: column; + + height: 100%; + + gap: 10px; + + padding: 10px 15px; + + .ant-btn { + align-items: center; + justify-content: flex-start; + + width: 100%; + + gap: 10vw; + + padding: 30px 20px; + + svg { + font-size: 1.2rem; + } + } + } + } + + .settings_list_render { + display: flex; + flex-direction: column; + + overflow-x: hidden; + + gap: 20px; + + .setting_item { + display: flex; + flex-direction: column; + + gap: 10px; + + .setting_item_header { + display: flex; + flex-direction: column; + + gap: 5px; + + .setting_item_info { + display: flex; + flex-direction: column; + + gap: 5px; + + .setting_item_header_title { + display: flex; + flex-direction: row; + + align-items: center; + justify-content: space-between; + + h1 { + font-size: 1rem; + margin: 0; + } + } + + .setting_item_header_description { + display: flex; + flex-direction: column; + + p { + margin: 0; + } + } + } + } + + .setting_item_content { + width: 100%; + padding: 5px 20px; + } + } + } + } +} \ No newline at end of file From f0d3bb266596685a222d830dc69046414ac7b2b8 Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Tue, 13 Jun 2023 22:42:42 +0000 Subject: [PATCH 4/4] added missing event --- packages/app/src/App.jsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/app/src/App.jsx b/packages/app/src/App.jsx index a7ad99c8..393d5911 100755 --- a/packages/app/src/App.jsx +++ b/packages/app/src/App.jsx @@ -339,6 +339,15 @@ class ComtyApp extends React.Component { app.eventBus.emit("layout.forceUpdate") app.eventBus.emit("router.forceUpdate") }, + "app.logout_request": () => { + antd.Modal.confirm({ + title: "Logout", + content: "Are you sure you want to logout?", + onOk: () => { + AuthModel.logout() + }, + }) + }, "app.no_session": async () => { const location = window.location.pathname @@ -452,7 +461,7 @@ class ComtyApp extends React.Component { app.eventBus.emit("statusTap") }) - StatusBar.setOverlaysWebView({ overlay: true }) + StatusBar.setOverlaysWebView({ overlay: false }) CapacitorApp.addListener("backButton", ({ canGoBack }) => { if (!canGoBack) {