diff --git a/ui/components/UserPreferences.js b/ui/components/UserPreferences.js index ad3acd2dd0d..f75dc6b635c 100644 --- a/ui/components/UserPreferences.js +++ b/ui/components/UserPreferences.js @@ -1,39 +1,36 @@ -//import useState from 'react'; -import React, { useState, useLayoutEffect } from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; -import { withRouter } from 'next/router'; -import { withStyles } from '@material-ui/core/styles'; -import { - FormControl, FormLabel, FormGroup, FormControlLabel, Switch -} from '@material-ui/core'; -import NoSsr from '@material-ui/core/NoSsr'; -import dataFetch from '../lib/data-fetch'; -import { updateUser, updateProgress, toggleCatalogContent } from '../lib/store'; -import Tabs from '@material-ui/core/Tabs'; -import Tab from '@material-ui/core/Tab'; -import { Paper, Tooltip } from '@material-ui/core'; -import SettingsRemoteIcon from '@material-ui/icons/SettingsRemote'; -import SettingsCellIcon from '@material-ui/icons/SettingsCell'; +//import useState from "react" +import React, { useState, useEffect } from "react"; +import { connect } from "react-redux"; +import { bindActionCreators } from "redux"; +import { withRouter } from "next/router"; +import { withStyles } from "@material-ui/core/styles"; +import { FormControl, FormLabel, FormGroup, FormControlLabel, Switch } from "@material-ui/core"; +import NoSsr from "@material-ui/core/NoSsr"; +import dataFetch from "../lib/data-fetch"; +import { updateUser, updateProgress, toggleCatalogContent } from "../lib/store"; +import Tabs from "@material-ui/core/Tabs"; +import Tab from "@material-ui/core/Tab"; +import { Paper, Tooltip } from "@material-ui/core"; +import SettingsRemoteIcon from "@material-ui/icons/SettingsRemote"; +import SettingsCellIcon from "@material-ui/icons/SettingsCell"; import ExtensionSandbox from "./ExtensionSandbox"; import RemoteComponent from "./RemoteComponent"; import ExtensionPointSchemaValidator from "../utils/ExtensionPointSchemaValidator"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faTachometerAlt } from '@fortawesome/free-solid-svg-icons'; -import MesherySettingsPerformanceComponent from './MesherySettingsPerformanceComponent'; -import { ctxUrl } from '../utils/multi-ctx'; -import { iconMedium } from '../css/icons.styles'; -import { getTheme,setTheme } from "../utils/theme"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faTachometerAlt } from "@fortawesome/free-solid-svg-icons"; +import MesherySettingsPerformanceComponent from "./MesherySettingsPerformanceComponent"; +import { ctxUrl } from "../utils/multi-ctx"; +import { iconMedium } from "../css/icons.styles"; +import { getTheme, setTheme } from "../utils/theme"; import { isExtensionOpen } from "../pages/_app"; -import { withNotify } from '../utils/hooks/useNotification'; -import { EVENT_TYPES } from '../lib/event-types'; - +import { EVENT_TYPES } from "../lib/event-types"; +import { useNotification } from "../utils/hooks/useNotification"; const styles = (theme) => ({ statsWrapper : { // padding : theme.spacing(2), maxWidth : "100%", - height : 'auto', + height : "auto", borderTopLeftRadius : 0, borderTopRightRadius : 0, borderBottomLeftRadius : 3, @@ -46,75 +43,77 @@ const styles = (theme) => ({ borderTopLeftRadius : 3, borderTopRightRadius : 3, }, - tabs : { marginLeft : 0, + tabs : { + marginLeft : 0, "& .MuiTabs-indicator" : { - backgroundColor : theme.palette.type === 'dark' ? "#00B39F" : theme.palette.primary, + backgroundColor : theme.palette.type === "dark" ? "#00B39F" : theme.palette.primary, }, }, tab : { - maxWidth : 'min(33%, 200px)', - minWidth : '50px', + maxWidth : "min(33%, 200px)", + minWidth : "50px", margin : 0, "&.Mui-selected" : { - color : theme.palette.type === 'dark' ? "#00B39F" : theme.palette.primary, - } + color : theme.palette.type === "dark" ? "#00B39F" : theme.palette.primary, + }, }, icon : { - display : 'inline', - verticalAlign : 'text-top', + display : "inline", + verticalAlign : "text-top", width : theme.spacing(1.75), marginLeft : theme.spacing(0.5), }, iconText : { - display : 'inline', - verticalAlign : 'middle', + display : "inline", + verticalAlign : "middle", }, - backToPlay : { margin : theme.spacing(2), }, - link : { cursor : 'pointer', }, + backToPlay : { margin : theme.spacing(2) }, + link : { cursor : "pointer" }, formContainer : { - display : 'flex', - 'flex-wrap' : 'wrap', - 'justify-content' : 'space-evenly', - padding : 50 + display : "flex", + "flex-wrap" : "wrap", + "justify-content" : "space-evenly", + padding : 50, }, formGrp : { padding : 20, - border : '1.5px solid #969696', - display : 'flex', + border : "1.5px solid #969696", + display : "flex", width : "70%", }, - formLegend : { fontSize : 20, }, + formLegend : { fontSize : 20 }, formLegendSmall : { fontSize : 16 }, switchBase : { - color : '#647881', - "&$checked" : { color : '#00b39f' }, - "&$checked + $track" : { backgroundColor : 'rgba(0,179,159,0.5)' }, + color : "#647881", + "&$checked" : { color : "#00b39f" }, + "&$checked + $track" : { backgroundColor : "rgba(0,179,159,0.5)" }, }, - track : { backgroundColor : 'rgba(100,120,129,0.5)', }, + track : { backgroundColor : "rgba(100,120,129,0.5)" }, checked : {}, tabLabel : { [theme.breakpoints.up("sm")] : { - fontSize : '1em' + fontSize : "1em", }, - [theme.breakpoints.between("xs", 'sm')] : { - fontSize : '0.8em' - } - } + [theme.breakpoints.between("xs", "sm")] : { + fontSize : "0.8em", + }, + }, }); -function ThemeToggler({ theme, themeSetter, notify, classes }) { +function ThemeToggler({ theme, themeSetter, classes }) { const [themeToggle, setthemeToggle] = useState(false); const defaultTheme = "light"; + const { notify } = useNotification(); const handle = () => { if (isExtensionOpen()) { return; } - theme === "dark" ? setthemeToggle(true) : setthemeToggle(false); - setTheme(theme) + theme === "dark" ? setthemeToggle(true) : setthemeToggle(false); + setTheme(theme); }; - useLayoutEffect(() => { + useEffect(() => { if (isExtensionOpen()) { if (getTheme() && getTheme() !== defaultTheme) { themeSetter(defaultTheme); @@ -123,17 +122,16 @@ function ThemeToggler({ theme, themeSetter, notify, classes }) { } themeSetter(getTheme() || defaultTheme); - }, []); - useLayoutEffect(handle, [theme]); + useEffect(handle, [theme]); const themeToggler = () => { if (isExtensionOpen()) { - notify({ message : "Toggling between themes is not supported in MeshMap", event_type : EVENT_TYPES.INFO }) + notify({ message : "Toggling between themes is not supported in MeshMap", event_type : EVENT_TYPES.INFO }); return; } - theme === "light" ? themeSetter("dark") : themeSetter("light"); + theme === "light" ? themeSetter("dark") : themeSetter("light"); }; return ( @@ -154,102 +152,109 @@ function ThemeToggler({ theme, themeSetter, notify, classes }) { ); } +function UserPreference(props) { + const [anonymousStats, setAnonymousStats] = useState(props.anonymousStats); + const [perfResultStats, setPerfResultStats] = useState(props.perfResultStats); + const [tabVal, setTabVal] = useState(0); + const [userPrefs, setUserPrefs] = useState(ExtensionPointSchemaValidator("user_prefs")()); + const [providerType, setProviderType] = useState(""); + const [catalogContent, setCatalogContent] = useState(true); + const [extensionPreferences, setExtensionPreferences] = useState({}); + const [capabilitiesLoaded, setCapabilitiesLoaded] = useState(false); -class UserPreference extends React.Component { - constructor(props) { - super(props); - this.state = { - anonymousStats : props.anonymousStats, - perfResultStats : props.perfResultStats, - tabVal : 0, - userPrefs : ExtensionPointSchemaValidator("user_prefs")(), - providerType : '', - catalogContent : true, - extensionPreferences : {}, - capabilitiesLoaded : false - }; - } + const { notify } = useNotification(); - handleCatalogContentToggle = () => { - this.props.toggleCatalogContent({ catalogVisibility : !this.state.catalogContent }); - this.setState((state) => ({ catalogContent : !state.catalogContent }), () => this.handleCatalogPreference()); - } + const handleCatalogContentToggle = () => { + props.toggleCatalogContent({ catalogVisibility : !catalogContent }); - handleCatalogPreference = () => { - let body = Object.assign({}, this.state.extensionPreferences) - body["catalogContent"] = this.state.catalogContent - dataFetch("/api/user/prefs", - { credentials : "include", - method : "POST", - body : JSON.stringify({ usersExtensionPreferences : body }) - }, + setCatalogContent(!catalogContent); + handleCatalogPreference(!catalogContent); + }; + + const handleCatalogPreference = (catalogContent) => { + let body = Object.assign({}, extensionPreferences); + body["catalogContent"] = catalogContent; + dataFetch( + "/api/user/prefs", + { credentials : "include", method : "POST", body : JSON.stringify({ usersExtensionPreferences : body }) }, () => { - const notify = this.props.notify; - notify({ message : `Catalog Content was ${this.state.catalogContent ? "enab" : "disab"}led`, event_type : EVENT_TYPES.SUCCESS }) + notify({ + message : `Catalog Content was ${catalogContent ? "enab" : "disab"}led`, + event_type : EVENT_TYPES.SUCCESS, + }); }, - this.handleError("There was an error sending your preference") - ) - } + handleError("There was an error sending your preference") + ); + }; - handleToggle = (name) => () => { - const self = this; - if (name === 'anonymousUsageStats') { - self.setState((state) => ({ anonymousStats : !state.anonymousStats }), () => this.handleChange(name)); + const handleToggle = (name) => () => { + if (name === "anonymousUsageStats") { + setAnonymousStats(!anonymousStats); + handleChange(name, !anonymousStats); } else { - self.setState((state) => ({ perfResultStats : !state.perfResultStats }), () => this.handleChange(name)); + setPerfResultStats(!perfResultStats); + handleChange(name, !perfResultStats); } - } + }; - handleError = (msg) => () => { - this.props.updateProgress({ showProgress : false }); - const notify = this.props.notify; - notify({ message : msg, event_type : EVENT_TYPES.ERROR }) - } + const handleError = (name) => () => { + props.updateProgress({ showProgress : false }); - handleChange = (name) => { - const self = this; - const { anonymousStats, perfResultStats } = this.state; - let val, msg; - if (name === 'anonymousUsageStats') { - val = anonymousStats; - msg = val - ? "Sending anonymous usage statistics was enabled" - : "Sending anonymous usage statistics was disabled"; + notify({ message : name, event_type : EVENT_TYPES.ERROR }); + }; + + const handleChange = (name, resultState) => { + let val = resultState, + msg; + if (name === "anonymousUsageStats") { + msg = val ? "Sending anonymous usage statistics was enabled" : "Sending anonymous usage statistics was disabled"; } else { - val = perfResultStats; msg = val ? "Sending anonymous performance results was enabled" : "Sending anonymous performance results was disabled"; } const requestBody = JSON.stringify({ - "anonymousUsageStats" : anonymousStats, - "anonymousPerfResults" : perfResultStats, + anonymousUsageStats : name === "anonymousUsageStats" ? val : anonymousStats, + anonymousPerfResults : name === "anonymousPerfResults" ? val : perfResultStats, }); // console.log(requestBody,anonymousStats,perfResultStats); - this.props.updateProgress({ showProgress : true }); + props.updateProgress({ showProgress : true }); dataFetch( - ctxUrl('/api/user/prefs', this.props.selectedK8sContexts), { - credentials : 'include', - method : 'POST', - headers : { 'Content-Type' : 'application/json;charset=UTF-8', }, + ctxUrl("/api/user/prefs", props.selectedK8sContexts), + { + credentials : "include", + method : "POST", + headers : { "Content-Type" : "application/json;charset=UTF-8" }, body : requestBody, - }, (result) => { - this.props.updateProgress({ showProgress : false }); - if (typeof result !== 'undefined') { - const notify = this.props.notify; - notify({ message : msg, event_type : val ? EVENT_TYPES.SUCCESS : EVENT_TYPES.INFO }) + }, + (result) => { + props.updateProgress({ showProgress : false }); + if (typeof result !== "undefined") { + // console.log(result); + + notify({ message : msg, event_type : val ? EVENT_TYPES.SUCCESS : EVENT_TYPES.INFO }); } - }, self.handleError('There was an error sending your preference')); - } + }, + handleError("There was an error sending your preference") + ); + }; - handleTabValChange = (event, newVal) => { - this.setState({ tabVal : newVal }); - } + const handleTabValChange = (event, newVal) => { + setTabVal(newVal); + }; - componentDidMount = () => { + useEffect(() => { + if (props.capabilitiesRegistry && !capabilitiesLoaded) { + setCapabilitiesLoaded(true); // to prevent re-compute + setUserPrefs(ExtensionPointSchemaValidator("user_prefs")(props.capabilitiesRegistry?.extensions?.user_prefs)); + setProviderType(props.capabilitiesRegistry?.provider_type); + } + }, [props.capabilitiesRegistry]); + + useEffect(() => { dataFetch( "/api/user/prefs", { @@ -258,138 +263,121 @@ class UserPreference extends React.Component { }, (result) => { if (result) { - this.setState({ - extensionPreferences : result?.usersExtensionPreferences, - catalogContent : result?.usersExtensionPreferences?.catalogContent - }) + // console.log(result); + setExtensionPreferences(result?.usersExtensionPreferences); + setCatalogContent(result?.usersExtensionPreferences?.catalogContent); } }, - err => console.error(err) - ) - - } - - componentDidUpdate() { - const { capabilitiesRegistry } = this.props; - if (capabilitiesRegistry && !this.state.capabilitiesLoaded) { - this.setState({ - capabilitiesLoaded : true, // to prevent re-compute - userPrefs : ExtensionPointSchemaValidator("user_prefs")(capabilitiesRegistry?.extensions?.user_prefs), - providerType : capabilitiesRegistry?.provider_type, - }) - } - } - - render() { - const { - anonymousStats, perfResultStats, tabVal, userPrefs, providerType, catalogContent - } = this.state; - const { classes } = this.props; - - // const mainIconScale = 'grow-10'; + (err) => console.error(err) + ); + }, []); - return ( - - - - - - } - label={General} - /> - - + return ( + + + + + } + label={General} + /> + + + } + label={Performance} + /> + + {/* NOTE: This tab's appearance is logical hence it must be put at last here! Otherwise added logic will need to be added for tab numbers!*/} + {userPrefs && providerType != "local" && ( + - } - label={Performance} + className={props.classes.tab} + icon={} + label={Remote Provider} /> - {/* NOTE: This tab's appearance is logical hence it must be put at last here! Otherwise added logic will need to be added for tab numbers!*/} - {userPrefs && providerType != 'local' && - - - } - label={Remote Provider} - /> - - } - - - - {tabVal === 0 && + )} + + + + {tabVal === 0 && ( <> -
- - Extensions +
+ + + Extensions + - )} + } labelPlacement="end" label="Meshery Catalog Content" />
-
- - Analytics and Improvement Program +
+ + + Analytics and Improvement Program + - )} + } labelPlacement="end" label="Send Anonymous Usage Statistics" /> - )} + } labelPlacement="end" label="Send Anonymous Performance Results" /> @@ -397,24 +385,18 @@ class UserPreference extends React.Component {
-
- - - Theme +
+ + + Theme - )} + control={ + + } labelPlacement="end" // label="Theme" /> @@ -422,36 +404,31 @@ class UserPreference extends React.Component {
- } - {tabVal === 1 && - - } - {tabVal === 2 && userPrefs && providerType !== 'local' && - RemoteComponent({ url })} /> - } - - - - ); - } + )} + {tabVal === 1 && } + {tabVal === 2 && userPrefs && providerType !== "local" && ( + RemoteComponent({ url })} /> + )} + + + ); } -const mapDispatchToProps = (dispatch) => ({ updateUser : bindActionCreators(updateUser, dispatch), - updateProgress : bindActionCreators(updateProgress, dispatch), toggleCatalogContent : bindActionCreators(toggleCatalogContent, dispatch) }); +const mapDispatchToProps = (dispatch) => ({ + updateUser : bindActionCreators(updateUser, dispatch), + updateProgress : bindActionCreators(updateProgress, dispatch), + toggleCatalogContent : bindActionCreators(toggleCatalogContent, dispatch), +}); const mapStateToProps = (state) => { - const selectedK8sContexts = state.get('selectedK8sContexts'); - const catalogVisibility = state.get('catalogVisibility'); - const capabilitiesRegistry = state.get("capabilitiesRegistry") + const selectedK8sContexts = state.get("selectedK8sContexts"); + const catalogVisibility = state.get("catalogVisibility"); + const capabilitiesRegistry = state.get("capabilitiesRegistry"); return { selectedK8sContexts, catalogVisibility, - capabilitiesRegistry + capabilitiesRegistry, }; }; - -export default withStyles(styles)(connect( - mapStateToProps, - mapDispatchToProps, -)(withRouter(withNotify(UserPreference)))); \ No newline at end of file +export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(withRouter(UserPreference))); diff --git a/ui/pages/user/preferences.js b/ui/pages/user/preferences.js index e764f3b7e1b..9fc335a7b98 100644 --- a/ui/pages/user/preferences.js +++ b/ui/pages/user/preferences.js @@ -2,84 +2,83 @@ import UserPreferences from "../../components/UserPreferences"; import { NoSsr, Paper, withStyles } from "@material-ui/core"; import { updatepagepath, updatepagetitle } from "../../lib/store"; import { connect } from "react-redux"; -import { bindActionCreators } from 'redux'; +import { bindActionCreators } from "redux"; import { getPath } from "../../lib/path"; -import Head from 'next/head'; -import dataFetch from '../../lib/data-fetch'; +import Head from "next/head"; +import dataFetch from "../../lib/data-fetch"; import { ctxUrl } from "../../utils/multi-ctx"; -import React from "react"; +import React, { useEffect, useState } from "react"; -const styles = { paper : { maxWidth : '90%', - margin : 'auto', - overflow : 'hidden', } }; +const styles = { paper : { maxWidth : "90%", margin : "auto", overflow : "hidden" } }; -class UserPref extends React.Component { - constructor(props){ - super(props); - this.state={}; - } +const UserPref = (props) => { + const [anonymousStats, setAnonymousStats] = useState(undefined); + const [perfResultStats, setPerfResultStats] = useState(undefined); - async componentDidMount() { - console.log(`path: ${getPath()}`); - this.props.updatepagepath({ path : getPath() }); - this.props.updatepagetitle({ title : "User Preferences" }); + useEffect(() => { + handleFetchData(); + }, [props]); - await new Promise(resolve => { + const handleFetchData = async () => { + // console.log(`path: ${getPath()}`); + props.updatepagepath({ path : getPath() }); + props.updatepagetitle({ title : "User Preferences" }); + + await new Promise((resolve) => { dataFetch( - ctxUrl('/api/user/prefs', this.props.selectedK8sContexts), + ctxUrl("/api/user/prefs", props.selectedK8sContexts), { - method : 'GET', - credentials : 'include', - }, (result) => { + method : "GET", + credentials : "include", + }, + (result) => { resolve(); - console.log(result); - if (typeof result !== 'undefined') { - this.setState({ - anonymousStats : result.anonymousUsageStats||false, - perfResultStats : result.anonymousPerfResults||false, - }); + if (typeof result !== "undefined") { + setAnonymousStats(result.anonymousUsageStats); + setPerfResultStats(result.anonymousPerfResults); } }, // Ignore error because we will fallback to default state // and to avoid try catch due to async await functions - resolve); + resolve + ); }); - } + }; - render () { - const { anonymousStats, perfResultStats }=this.state; - console.log(this.state) - if (anonymousStats===undefined){ - // Skip rendering till data is not loaded - return
- } - return ( - - - Preferences | Meshery - - - {/* {should meshmap specific user preferences be placed along with general preferences or from the remote provider} */} - - - - ); - } -} + return ( + <> + {anonymousStats === undefined || perfResultStats === undefined ? ( +
+ ) : ( + + + Preferences | Meshery + + + {/* {should meshmap specific user preferences be placed along with general preferences or from the remote provider} */} + + + + )} + + ); +}; -const mapDispatchToProps = dispatch => ({ +const mapDispatchToProps = (dispatch) => ({ updatepagepath : bindActionCreators(updatepagepath, dispatch), updatepagetitle : bindActionCreators(updatepagetitle, dispatch), -}) +}); const mapStateToProps = (state) => { - const selectedK8sContexts = state.get('selectedK8sContexts'); + const selectedK8sContexts = state.get("selectedK8sContexts"); return { selectedK8sContexts, }; }; -export default withStyles(styles)(connect( - mapStateToProps, - mapDispatchToProps -)(UserPref)); \ No newline at end of file +export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(UserPref));