update settigns

This commit is contained in:
SrGooglo 2023-02-24 14:26:19 +00:00
parent 108f99c897
commit 34f4623a36
19 changed files with 957 additions and 246 deletions

View File

@ -0,0 +1,109 @@
import React from "react"
import * as antd from "antd"
import moment from "moment"
import { Icons } from "components/Icons"
import config from "config"
import "./index.less"
const Footer = (props) => {
const isDevMode = window.__evite?.env?.NODE_ENV !== "production"
return <div className="footer">
<div>
<div>{config.app?.siteName}</div>
<div>
<antd.Tag>
<Icons.Tag />v{window.app.version}
</antd.Tag>
</div>
<div>
<antd.Tag color={isDevMode ? "magenta" : "green"}>
{isDevMode ? <Icons.Triangle /> : <Icons.Box />}
{isDevMode ? "development" : "stable"}
</antd.Tag>
</div>
</div>
</div>
}
export default {
id: "about",
icon: "Info",
label: "About",
group: "bottom",
render: () => {
const isProduction = import.meta.env.PROD
const [serverManifest, setServerManifest] = React.useState(null)
const checkServerVersion = async () => {
const serverManifest = await app.cores.api.customRequest("main")
setServerManifest(serverManifest.data)
}
React.useEffect(() => {
checkServerVersion()
}, [])
return <div className="about_app">
<div className="header">
<div className="branding">
<div className="logo">
<img
src={config.logo.alt}
alt="Logo"
/>
</div>
<div className="texts">
<h2>{config.app.siteName}</h2>
<span>{config.author}</span>
<span> Licensed with {config.package?.license ?? "unlicensed"} </span>
</div>
</div>
<div className="versions">
<antd.Tag><Icons.Tag />v{window.app.version ?? "experimental"}</antd.Tag>
<antd.Tag color={isProduction ? "green" : "magenta"}>
{isProduction ? <Icons.CheckCircle /> : <Icons.Triangle />}
{String(import.meta.env.MODE)}
</antd.Tag>
</div>
</div>
<div className="group">
<h3><Icons.Server />Server info</h3>
<div className="field">
Powered by Linebridge
<div className="value">
<antd.Tag>v{serverManifest?.LINEBRIDGE_SERVER_VERSION ?? "Unknown"}</antd.Tag>
</div>
</div>
<div className="field">
<span>
<Icons.Globe /> Origin address
</span>
<div className="value">
{app.cores.api?.namespaces.main.origin ?? "Unknown"}
</div>
</div>
<div className="field">
<span>
<Icons.Clock /> Server Time
</span>
<div className="value">
{moment(serverManifest?.requestTime).format("YYYY-MM-DD HH:mm:ss")}
</div>
</div>
</div>
<Footer />
</div>
}
}

View File

@ -0,0 +1,93 @@
.about_app {
display: flex;
flex-direction: column;
width: 100%;
padding: 0 20px 20px 20px;
background-color: var(--background-color-accent);
color: var(--text-color);
border-radius: 12px;
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-self: center;
width: 100%;
margin-bottom: 20px;
padding: 20px 0;
border-bottom: 1px solid var(--border-color);
.branding {
display: flex;
flex-direction: row;
align-items: center;
.logo {
width: 60px;
height: 100%;
margin-right: 20px;
img {
width: 100%;
height: 100%;
}
}
.texts {
display: flex;
flex-direction: column;
}
h1,
h2,
h3 {
height: fit-content;
line-height: 24px;
}
span {
height: fit-content;
color: var(--background-color-contrast);
font-size: 10px;
}
}
}
.group {
display: inline-flex;
flex-direction: column;
justify-content: center;
margin-bottom: 10px;
.field {
display: inline-flex;
flex-direction: column;
margin-bottom: 10px;
font-size: 0.9rem;
.value {
font-size: 0.8rem;
display: inline-flex;
flex-direction: row;
margin-left: 10px;
}
}
}
}

View File

@ -1,173 +1,477 @@
import React from "react"
import loadable from "@loadable/component"
import { Modal } from "antd"
import UploadButton from "../components/uploadButton"
import "./index.less"
export default {
id: "apparence",
icon: "Eye",
label: "Apparence",
group: "app",
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": "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: "style.reduceAnimations",
group: "animations",
component: "Switch",
icon: "MdOutlineSlowMotionVideo",
title: "Reduce animations",
experimental: true,
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": {
id: "style.pageTransitionDuration",
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,
marks: {
[app.cores.style.defaultVar("page-transition-duration").replace("ms", "")]: " ",
},
tooltip: {
formatter: (value) => `${value / 1000}s`
}
},
"emitEvent": "modifyTheme",
"emissionValueUpdate": (value) => {
return {
defaultValue: () => {
const value = app.cores.style.getValue("page-transition-duration")
return value ? Number(value.replace("ms", "")) : 250
},
onUpdate: (value) => {
app.cores.style.modify({
"page-transition-duration": `${value}ms`
}
})
},
storaged: true,
},
{
"id": "auto_darkMode",
"experimental": true,
"storaged": true,
"group": "aspect",
"component": "Switch",
"icon": "Moon",
"title": "Auto dark mode",
"emitEvent": "style.autoDarkModeToogle",
id: "style.auto_darkMode",
group: "aspect",
component: "Switch",
icon: "Moon",
title: "Sync with system",
description: "Automatically switch to dark mode based on your system preference.",
emitEvent: "style.autoDarkModeToogle",
storaged: true,
},
{
"experimental": true,
"dependsOn": {
"auto_darkMode": false
id: "style.darkMode",
group: "aspect",
component: "Switch",
icon: "Moon",
title: "Dark mode",
description: "Change the theme variant of the application to dark.",
dependsOn: {
"style.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"
defaultValue: () => {
return app.cores.style.currentVariant === "dark"
},
onUpdate: (value) => {
app.cores.style.modify({
themeVariant: value ? "dark" : "light"
})
return value
},
storaged: true
},
{
"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: "style.compactMode",
group: "aspect",
component: "Switch",
icon: "MdOutlineViewCompact",
title: "Compact mode",
description: "Reduce the size of the application elements.",
defaultValue: () => {
return app.cores.style.getValue("compact-mode")
},
onUpdate: (value) => {
app.cores.style.modify({
"compact-mode": value
})
return value
},
storaged: 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,
id: "style.uiFont",
group: "aspect",
component: "Select",
icon: "MdOutlineFontDownload",
title: "UI font",
description: "Change the font of the application.",
props: {
style: {
width: "100%"
},
options: [
{
label: "Varela Round (Default)",
value: "'Varela Round', sans-serif"
},
{
label: "Inter",
value: "'Inter', sans-serif"
},
]
},
"emitEvent": "modifyTheme",
"emissionValueUpdate": (value) => {
return {
defaultValue: () => {
return app.cores.style.getValue("fontFamily")
},
onUpdate: (value) => {
app.cores.style.modify({
"fontFamily": value
})
return value
},
storaged: true
},
{
id: "style.colorPrimary",
group: "aspect",
component: "SliderColorPicker",
title: "Primary color",
description: "Change primary color of the application.",
defaultValue: () => {
return app.cores.style.getValue("colorPrimary")
},
onUpdate: (value) => {
app.cores.style.modify({
"colorPrimary": value
})
},
storaged: false,
},
{
id: "style.parallaxBackground",
group: "aspect",
component: "Switch",
icon: "MdOutline3DRotation",
title: "Parallax background",
description: "Create a parallax effect on the background.",
storaged: true,
},
{
id: "style.backgroundImage",
group: "aspect",
icon: "MdOutlineImage",
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/urlInput")),
props: {
noPreview: true,
},
extraActions: [
{
id: "delete",
icon: "Delete",
title: "Remove",
onClick: (ctx) => {
return ctx.dispatchUpdate("")
}
},
UploadButton
],
defaultValue: () => {
const value = app.cores.style.getValue("backgroundImage")
console.log(value)
return value ? value.replace(/url\(|\)/g, "") : ""
},
onUpdate: (value) => {
app.cores.style.modify({
backgroundImage: `url(${value})`
}
})
},
storaged: false,
},
{
"id": "backgroundBlur",
"storaged": true,
"group": "aspect",
"component": "Slider",
"icon": "Eye",
"title": "Background blur",
"description": "Create a blur effect on the background.",
"props": {
id: "style.backgroundPattern",
group: "aspect",
icon: "MdGrid4X4",
component: loadable(() => import("../components/backgroundSelector")),
title: "Background pattern",
description: "Change background pattern of the application.",
extraActions: [
{
id: "remove",
icon: "Delete",
title: "Remove",
onClick: () => {
app.cores.style.modify({
backgroundSVG: "unset"
})
}
}
],
storaged: false,
},
{
id: "style.backgroundBlur",
group: "aspect",
component: "Slider",
icon: "MdBlurOn",
title: "Background blur",
description: "Create a blur effect on the background.",
props: {
min: 0,
max: 50,
step: 5
step: 1
},
"emitEvent": "modifyTheme",
"emissionValueUpdate": (value) => {
return {
backgroundBlur: `${value}px`,
}
defaultValue: () => {
const value = app.cores.style.getValue("backgroundBlur")
return value ? parseInt(value.replace("px", "")) : 0
},
onUpdate: (value) => {
app.cores.style.modify({
backgroundBlur: `${value}px`
})
},
storaged: false,
},
{
"id": "backgroundColorTransparency",
"storaged": true,
"group": "aspect",
"component": "Slider",
"icon": "Eye",
"title": "Background color transparency",
"description": "Adjust the transparency of the background color.",
"props": {
id: "style.backgroundColorTransparency",
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,
}
defaultValue: () => {
const value = app.cores.style.getValue("backgroundColorTransparency")
return value ? parseFloat(value) : 1
},
onUpdate: (value) => {
app.cores.style.modify({
backgroundColorTransparency: value
})
},
storaged: false
},
{
"id": "resetTheme",
"storaged": true,
"group": "aspect",
"component": "Button",
"title": "Reset theme",
"props": {
"children": "Default Theme"
id: "style.backgroundSize",
group: "aspect",
component: "Select",
icon: "MdOutlineImageAspectRatio",
title: "Background size",
description: "Adjust the size of the background image.",
props: {
style: {
width: "100%"
},
options: [
{
label: "Cover",
value: "cover"
},
{
label: "Contain",
value: "contain"
},
{
label: "Auto",
value: "auto"
},
{
label: "50%",
value: "50%"
},
{
label: "100%",
value: "100%"
},
{
label: "150%",
value: "150%"
},
]
},
"emitEvent": "resetTheme",
"noUpdate": true,
defaultValue: () => {
return app.cores.style.getValue("backgroundSize")
},
onUpdate: (value) => {
app.cores.style.modify({
backgroundSize: value
})
return value
},
storaged: false
},
{
id: "style.backgroundPosition",
group: "aspect",
component: "Select",
icon: "MdOutlineImageAspectRatio",
title: "Background position",
description: "Adjust the position of the background image.",
props: {
style: {
width: "100%"
},
options: [
{
label: "Left",
value: "left"
},
{
label: "Center",
value: "center"
},
{
label: "Right",
value: "right"
},
{
label: "Top",
value: "top"
},
]
},
defaultValue: () => {
return app.cores.style.getValue("backgroundPosition")
},
onUpdate: (value) => {
app.cores.style.modify({
backgroundPosition: value
})
return value
},
storaged: false
},
{
id: "style.backgroundRepeat",
group: "aspect",
component: "Select",
icon: "MdOutlineImageAspectRatio",
title: "Background repeat",
description: "Adjust the repeat of the background image.",
props: {
style: {
width: "100%"
},
options: [
{
label: "Repeat",
value: "repeat"
},
{
label: "No repeat",
value: "no-repeat"
},
{
label: "Repeat X",
value: "repeat-x"
},
{
label: "Repeat Y",
value: "repeat-y"
},
]
},
defaultValue: () => {
return app.cores.style.getValue("backgroundRepeat")
},
onUpdate: (value) => {
app.cores.style.modify({
backgroundRepeat: value
})
return value
},
storaged: false
},
{
id: "style.backgroundAttachment",
group: "aspect",
component: "Select",
icon: "MdOutlineImageAspectRatio",
title: "Background attachment",
description: "Adjust the attachment of the background image.",
props: {
style: {
width: "100%"
},
options: [
{
label: "Scroll",
value: "scroll"
},
{
label: "Fixed",
value: "fixed"
},
{
label: "Local",
value: "local"
},
{
label: "Initial",
value: "initial"
},
{
label: "Inherit",
value: "inherit"
},
]
},
defaultValue: () => {
return app.cores.style.getValue("backgroundAttachment")
},
onUpdate: (value) => {
app.cores.style.modify({
backgroundAttachment: value
})
return value
},
storaged: false
},
{
id: "resetTheme",
group: "aspect",
component: "Button",
title: "Reset theme",
props: {
children: "Default Theme"
},
onUpdate: (value) => {
Modal.confirm({
title: "Are you sure you want to reset the theme to the default theme ?",
description: "This action will reset the theme to the default theme. All your modifications will be lost.",
onOk: () => {
app.cores.style.setDefault()
}
})
},
storaged: false
}
]
}

View File

@ -1,71 +0,0 @@
import React from "react"
import { Button, Input, Upload } from "antd"
import { Icons } from "components/Icons"
import "./index.less"
export default (props) => {
const [value, setValue] = React.useState(props.ctx.currentValue)
const [uploading, setUploading] = React.useState(false)
const uploadImage = async (req) => {
setUploading(true)
const formData = new FormData()
formData.append("files", req.file)
const request = await window.app.api.withEndpoints("main").post.upload(formData, undefined).catch((error) => {
console.error(error)
app.message.error(error)
return false
})
setUploading(false)
if (request) {
setValue(request.files[0].url)
props.ctx.dispatchUpdate(request.files[0].url)
}
}
return <div className="imageUploader">
{
!props.noPreview && value && <div className="uploadPreview">
<img src={value} />
</div>
}
<Input.Group compact>
<Input
placeholder="Image URL..."
value={value}
onChange={(e) => setValue(e.target.value)}
onPressEnter={() => props.ctx.dispatchUpdate(value)}
/>
<Button
icon={<Icons.Save />}
onClick={() => props.ctx.dispatchUpdate(value)}
/>
</Input.Group>
or
<Upload
customRequest={uploadImage}
multiple={false}
accept="image/*"
progress={false}
fileList={[]}
>
<Button
icon={<Icons.Upload />}
loading={uploading}
>
Upload
</Button>
</Upload>
</div>
}

View File

@ -0,0 +1,57 @@
import React from "react"
import SVG from "react-inlinesvg"
import "./index.less"
const defaultBackgrounds = [
{
id: "topography",
label: "Topography",
src: "/assets/default_bg/topography.svg"
},
{
id: "meteors",
label: "Meteors",
src: "/assets/default_bg/meteors.svg"
},
{
id: "dots",
label: "Dots",
src: "/assets/default_bg/dots.svg"
},
{
id: "hideout",
label: "Hideout",
src: "/assets/default_bg/hideout.svg"
}
]
export default (props) => {
return <div className="background_selector">
<div className="background_selector_defaults">
{
defaultBackgrounds.map((background) => {
return <div className="background_selector_defaults__item">
<div className="background_selector_defaults__item_name">
<h3>{background.label}</h3>
</div>
<div
className="background_selector_defaults__item_preview"
onClick={() => {
app.cores.style.modify({
backgroundSVG: `url("${background.src}")`
})
}}
style={{
maskImage: `url("${background.src}")`,
WebkitMaskImage: `url("${background.src}")`
}}
/>
</div>
})
}
</div>
</div>
}

View File

@ -0,0 +1,65 @@
.background_selector {
display: flex;
flex-direction: column;
.background_selector_defaults {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
overflow-x: auto;
.background_selector_defaults__item {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
width: 10vw;
height: 300px;
transition: all 150ms ease-in-out;
margin-right: 20px;
background-color: var(--background-color-accent);
border-radius: 10px;
.background_selector_defaults__item_name {
display: flex;
flex-direction: row;
font-size: 1rem;
font-weight: 500;
margin: 10px;
}
.background_selector_defaults__item_preview {
width: 100%;
height: 100%;
overflow: hidden;
border-radius: 10px;
cursor: pointer;
background-color: var(--text-color);
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
}
&:last-child {
margin-right: 0;
}
}
}
}

View File

@ -1,7 +1,7 @@
import React from "react"
import * as antd from "antd"
import { User } from "models"
import { UserModel } from "models"
import { Icons } from "components/Icons"
import "./index.less"
@ -31,7 +31,7 @@ const ChangePasswordComponent = (props) => {
setError(null)
setLoading(true)
const result = await User.changePassword({ currentPassword, newPassword }).catch((err) => {
const result = await UserModel.changePassword({ currentPassword, newPassword }).catch((err) => {
console.error(err)
setError(err.response.data.message)
return null

View File

@ -0,0 +1,69 @@
import React from "react"
import { Button, Upload } from "antd"
import { Icons } from "components/Icons"
export default (props) => {
const [uploading, setUploading] = React.useState(false)
const handleUpload = async (req) => {
console.log(req)
setUploading(true)
const formData = new FormData()
formData.append("files", req.file)
const request = await window.app.cores.api.customRequest({
url: "/upload",
method: "POST",
data: formData
}).catch((error) => {
console.error(error)
app.message.error(error.respose.data.message)
return false
})
setUploading(false)
if (request) {
// check failed uploads
if (request.failed.length > 0) {
request.failed.forEach((file) => {
app.notification.error({
message: "Failed to upload file",
description: `Could not upload file ${file.fileName} cause > ${file.error}`
})
})
}
props.ctx.dispatchUpdate(request.files[0].url)
}
}
return <Upload
customRequest={handleUpload}
multiple={false}
accept="image/*"
progress={false}
fileList={[]}
>
<Button
icon={props.icon ?? <Icons.Upload
style={{
margin: 0
}}
/>}
loading={uploading}
type={
props.type ?? "round"
}
>
{
props.children ?? "Upload"
}
</Button>
</Upload>
}

View File

@ -0,0 +1,31 @@
import React from "react"
import { Button, Input } from "antd"
import { Icons } from "components/Icons"
import "./index.less"
export default (props) => {
const [value, setValue] = React.useState(props.ctx.currentValue)
return <div className="imageUploader">
{
!props.noPreview && value && <div className="uploadPreview">
<img src={value} />
</div>
}
<Input.Group compact>
<Input
placeholder="Image URL..."
value={value}
onChange={(e) => setValue(e.target.value)}
onPressEnter={() => props.ctx.dispatchUpdate(value)}
/>
<Button
icon={<Icons.Save />}
onClick={() => props.ctx.dispatchUpdate(value)}
/>
</Input.Group>
</div>
}

View File

@ -2,8 +2,10 @@ import React from "react"
import loadable from "@loadable/component"
export default {
id: "extensions",
icon: "MdOutlineCode",
label: "Extensions",
group: "advanced",
settings: [
]

View File

@ -3,8 +3,10 @@ import config from "config"
import { Select } from "antd"
export default {
id: "general",
icon: "Command",
label: "App",
label: "General",
group: "app",
settings: [
{
"id": "language",

View File

@ -1,17 +1,24 @@
import AppSettings from "./app"
import GeneralSettings from "./general"
import ProfileSettings from "./profile"
import SecuritySettings from "./security"
import SubcriptionsSettings from "./subscriptions"
import NotificationsSettings from "./notifications"
import ApparenceSettings from "./apparence"
import ExtensionsSettings from "./extensions"
import SyncSettings from "./sync"
import PlayerSettings from "./player"
import AboutPage from "./about"
export default {
app: AppSettings,
general: GeneralSettings,
profile: ProfileSettings,
apparence: ApparenceSettings,
player: PlayerSettings,
security: SecuritySettings,
notifications: NotificationsSettings,
extensions: ExtensionsSettings,
sync: SyncSettings,
subscriptions: SubcriptionsSettings,
about: AboutPage,
}

View File

@ -1,8 +1,10 @@
import React from "react"
export default {
id: "notifications",
icon: "Bell",
label: "Notifications",
group: "basic",
settings: [
]

View File

@ -0,0 +1,40 @@
export default {
id: "player",
icon: "PlayCircleOutlined",
label: "Player",
group: "app",
settings: [
{
id: "player.allowVolumeOver100",
label: "Allow volume over 100%",
description: "Allow volume amplification over 100% (may cause distortion)",
component: "Switch",
storaged: true,
},
{
id: "player.crossfade",
label: "Crossfade",
description: "Enable crossfade between tracks",
component: "Slider",
props: {
min: 0,
max: 10,
step: 0.1,
marks: {
0: "Off",
1: "1s",
2: "2s",
3: "3s",
4: "4s",
5: "5s",
6: "6s",
7: "7s",
8: "8s",
9: "9s",
10: "10s",
},
},
storaged: true,
}
]
}

View File

@ -1,12 +1,15 @@
import React from "react"
import { User } from "models"
import { UserModel } from "models"
import loadable from "@loadable/component"
import UploadButton from "../components/uploadButton"
export default {
id: "profile",
icon: "User",
label: "Profile",
group: "basic",
ctxData: async () => {
const userData = await User.data()
const userData = await UserModel.data()
return {
userData
@ -26,47 +29,43 @@ export default {
},
},
{
"id": "fullName",
"group": "account.basicInfo",
"component": "Input",
"icon": "Edit3",
"title": "Name",
"description": "Change your public name",
"props": {
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) => {
defaultValue: (ctx) => {
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
}
onUpdate: async (value) => {
const result = await UserModel.updateData({
fullName: value
})
if (result) {
return result
}
},
"extraActions": [
extraActions: [
{
"id": "unset",
"icon": "Delete",
"title": "Unset",
"onClick": async () => {
window.app.api.withEndpoints("main").post.unsetPublicName()
await UserModel.unsetFullName()
}
}
],
"debounced": true,
debounced: true,
storaged: false,
},
{
"id": "email",
@ -85,13 +84,8 @@ export default {
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 = await UserModel.updateData({
email: value
})
if (result) {
@ -106,18 +100,16 @@ export default {
"icon": "Image",
"title": "Avatar",
"description": "Change your avatar (Upload an image or use an URL)",
"component": loadable(() => import("../components/ImageUploader")),
"component": loadable(() => import("../components/urlInput")),
extraActions: [
UploadButton
],
"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 = await UserModel.updateData({
avatar: value
})
if (result) {
@ -133,18 +125,16 @@ export default {
"icon": "Image",
"title": "Cover",
"description": "Change your profile cover (Upload an image or use an URL)",
"component": loadable(() => import("../components/ImageUploader")),
"component": loadable(() => import("../components/urlInput")),
extraActions: [
UploadButton
],
"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 = await UserModel.updateData({
cover: value
})
if (result) {
@ -171,13 +161,8 @@ export default {
return ctx.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 = await UserModel.updateData({
description: value
})
if (result) {
@ -185,6 +170,7 @@ export default {
}
},
"debounced": true,
storaged: false,
},
]
}

View File

@ -1,11 +1,14 @@
import React from "react"
import loadable from "@loadable/component"
import AuthModel from "models/auth"
// TODO: Make logout button require a valid session to be not disabled
export default {
id: "security",
icon: "Shield",
label: "Security",
group: "basic",
settings: [
{
"id": "change-password",
@ -39,7 +42,9 @@ export default {
"icon": "LogOut",
"title": "Logout",
"description": "Logout from your account",
"emitEvent": "session.logout",
onUpdate: async () => {
await AuthModel.logout()
}
}
]
}

View File

@ -0,0 +1,8 @@
export default {
id: "subscriptions",
label: "Subscriptions",
icon: "MdLoyalty",
group: "other",
settings: [
]
}

View File

@ -5,8 +5,10 @@ import SyncModel from "models/sync"
// TODO: Make logout button require a valid session to be not disabled
export default {
id: "sync",
icon: "MdSync",
label: "Sync",
group: "advanced",
ctxData: async () => {
const spotifyAccount = await SyncModel.spotifyCore.getData().catch((err) => {
return null