From 7d3bbf42405fee4095b3326f65d19be29e077a7d Mon Sep 17 00:00:00 2001 From: rjwats Date: Sat, 16 May 2020 12:39:18 +0100 Subject: [PATCH] UI Usability Fixes * Fallback to sessionStorage if localStorage is absent * Disable auto-correct and auto-capitalize on username field (SignIn) * Fix SignIn component name * Improve support for low screen widths Co-authored-by: kasedy --- interface/src/SignIn.tsx | 14 +- .../src/authentication/Authentication.ts | 23 ++-- .../authentication/AuthenticationWrapper.tsx | 8 +- interface/src/project/DemoInformation.tsx | 120 +++++++----------- interface/src/security/ManageUsersForm.tsx | 16 +-- interface/src/wifi/WiFiStatusForm.tsx | 2 +- 6 files changed, 84 insertions(+), 99 deletions(-) diff --git a/interface/src/SignIn.tsx b/interface/src/SignIn.tsx index ebe66da..85cf031 100644 --- a/interface/src/SignIn.tsx +++ b/interface/src/SignIn.tsx @@ -39,17 +39,17 @@ const styles = (theme: Theme) => createStyles({ } }); -type SignInPageProps = WithSnackbarProps & WithStyles & AuthenticationContextProps; +type SignInProps = WithSnackbarProps & WithStyles & AuthenticationContextProps; -interface SignInPageState { +interface SignInState { username: string, password: string, processing: boolean } -class SignInPage extends Component { +class SignIn extends Component { - constructor(props: SignInPageProps) { + constructor(props: SignInProps) { super(props); this.state = { username: '', @@ -115,6 +115,10 @@ class SignInPage extends Component { value={username} onChange={this.updateInputElement} margin="normal" + inputProps={{ + autoCapitalize: "none", + autoCorrect: "off", + }} /> { } -export default withAuthenticationContext(withSnackbar(withStyles(styles)(SignInPage))); +export default withAuthenticationContext(withSnackbar(withStyles(styles)(SignIn))); diff --git a/interface/src/authentication/Authentication.ts b/interface/src/authentication/Authentication.ts index 17310b7..8b3eaf3 100644 --- a/interface/src/authentication/Authentication.ts +++ b/interface/src/authentication/Authentication.ts @@ -7,21 +7,28 @@ export const ACCESS_TOKEN = 'access_token'; export const LOGIN_PATHNAME = 'loginPathname'; export const LOGIN_SEARCH = 'loginSearch'; +/** + * Fallback to sessionStorage if localStorage is absent. WebView may not have local storage enabled. + */ +export function getStorage() { + return localStorage || sessionStorage; +} + export function storeLoginRedirect(location?: H.Location) { if (location) { - localStorage.setItem(LOGIN_PATHNAME, location.pathname); - localStorage.setItem(LOGIN_SEARCH, location.search); + getStorage().setItem(LOGIN_PATHNAME, location.pathname); + getStorage().setItem(LOGIN_SEARCH, location.search); } } export function clearLoginRedirect() { - localStorage.removeItem(LOGIN_PATHNAME); - localStorage.removeItem(LOGIN_SEARCH); + getStorage().removeItem(LOGIN_PATHNAME); + getStorage().removeItem(LOGIN_SEARCH); } export function fetchLoginRedirect(): H.LocationDescriptorObject { - const loginPathname = localStorage.getItem(LOGIN_PATHNAME); - const loginSearch = localStorage.getItem(LOGIN_SEARCH); + const loginPathname = getStorage().getItem(LOGIN_PATHNAME); + const loginSearch = getStorage().getItem(LOGIN_SEARCH); clearLoginRedirect(); return { pathname: loginPathname || `/${PROJECT_PATH}/`, @@ -33,7 +40,7 @@ export function fetchLoginRedirect(): H.LocationDescriptorObject { * Wraps the normal fetch routene with one with provides the access token if present. */ export function authorizedFetch(url: RequestInfo, params?: RequestInit): Promise { - const accessToken = localStorage.getItem(ACCESS_TOKEN); + const accessToken = getStorage().getItem(ACCESS_TOKEN); if (accessToken) { params = params || {}; params.credentials = 'include'; @@ -63,7 +70,7 @@ export function redirectingAuthorizedFetch(url: RequestInfo, params?: RequestIni } export function addAccessTokenParameter(url: string) { - const accessToken = localStorage.getItem(ACCESS_TOKEN); + const accessToken = getStorage().getItem(ACCESS_TOKEN); if (!accessToken) { return url; } diff --git a/interface/src/authentication/AuthenticationWrapper.tsx b/interface/src/authentication/AuthenticationWrapper.tsx index a73eaa9..21584a4 100644 --- a/interface/src/authentication/AuthenticationWrapper.tsx +++ b/interface/src/authentication/AuthenticationWrapper.tsx @@ -8,7 +8,7 @@ import { withStyles, Theme, createStyles, WithStyles } from '@material-ui/core/s import history from '../history' import { VERIFY_AUTHORIZATION_ENDPOINT } from '../api'; -import { ACCESS_TOKEN, authorizedFetch } from './Authentication'; +import { ACCESS_TOKEN, authorizedFetch, getStorage } from './Authentication'; import { AuthenticationContext, Me } from './AuthenticationContext'; export const decodeMeJWT = (accessToken: string): Me => jwtDecode(accessToken); @@ -81,7 +81,7 @@ class AuthenticationWrapper extends React.Component { - const accessToken = localStorage.getItem(ACCESS_TOKEN) + const accessToken = getStorage().getItem(ACCESS_TOKEN) if (accessToken) { authorizedFetch(VERIFY_AUTHORIZATION_ENDPOINT) .then(response => { @@ -100,7 +100,7 @@ class AuthenticationWrapper extends React.Component { try { - localStorage.setItem(ACCESS_TOKEN, accessToken); + getStorage().setItem(ACCESS_TOKEN, accessToken); const me: Me = decodeMeJWT(accessToken); this.setState({ context: { ...this.state.context, me } }); this.props.enqueueSnackbar(`Logged in as ${me.username}`, { variant: 'success' }); @@ -111,7 +111,7 @@ class AuthenticationWrapper extends React.Component { - localStorage.removeItem(ACCESS_TOKEN); + getStorage().removeItem(ACCESS_TOKEN); this.setState({ context: { ...this.state.context, diff --git a/interface/src/project/DemoInformation.tsx b/interface/src/project/DemoInformation.tsx index 0573799..7beff19 100644 --- a/interface/src/project/DemoInformation.tsx +++ b/interface/src/project/DemoInformation.tsx @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import { Typography, TableRow, TableBody, TableCell, TableHead, Table, Box } from '@material-ui/core'; +import { Typography, Box, List, ListItem, ListItemText } from '@material-ui/core'; import { SectionContent } from '../components'; class DemoInformation extends Component { @@ -17,78 +17,52 @@ class DemoInformation extends Component { simplify merges should you wish to update your project with future framework changes. - The demo project interface code stored in the interface/project directory: - - - - - - File - - - Description - - - - - - - ProjectMenu.tsx - - - You can add your project's screens to the side bar here. - - - - - ProjectRouting.tsx - - - The routing which controls the screens of your project. - - - - - DemoProject.tsx - - - This screen, with tabs and tab routing. - - - - - DemoInformation.tsx - - - The demo information page. - - - - - LightStateRestController.tsx - - - A form which lets the user control the LED over a REST service. - - - - - LightStateWebSocketController.tsx - - - A form which lets the user control and monitor the status of the LED over WebSockets. - - - - - LightMqttSettingsController.tsx - - - A form which lets the user change the MQTT settings for MQTT based control of the LED. - - - -
+ The demo project interface code is stored in the interface/project directory: + + + + + + + + + + + + + + + + + + + + + + + + See the project README for a full description of the demo project. diff --git a/interface/src/security/ManageUsersForm.tsx b/interface/src/security/ManageUsersForm.tsx index 78d1dec..fc73577 100644 --- a/interface/src/security/ManageUsersForm.tsx +++ b/interface/src/security/ManageUsersForm.tsx @@ -1,7 +1,7 @@ import React, { Fragment } from 'react'; import { ValidatorForm } from 'react-material-ui-form-validator'; -import { Table, TableBody, TableCell, TableHead, TableFooter, TableRow } from '@material-ui/core'; +import { Table, TableBody, TableCell, TableHead, TableFooter, TableRow, withWidth, WithWidthProps, isWidthDown } from '@material-ui/core'; import { Box, Button, Typography, } from '@material-ui/core'; import EditIcon from '@material-ui/icons/Edit'; @@ -28,7 +28,7 @@ function compareUsers(a: User, b: User) { return 0; } -type ManageUsersFormProps = RestFormProps & AuthenticatedContextProps; +type ManageUsersFormProps = RestFormProps & AuthenticatedContextProps & WithWidthProps; type ManageUsersFormState = { creating: boolean; @@ -106,12 +106,12 @@ class ManageUsersForm extends React.Component - +
Username @@ -141,12 +141,12 @@ class ManageUsersForm extends React.Component ))} - + - + @@ -188,4 +188,4 @@ class ManageUsersForm extends React.Component { - +