Skip to content

Commit

Permalink
Merge branch 'bot-mobile-ux' into 'master'
Browse files Browse the repository at this point in the history
Bot UI: Enhance UX on mobile

See merge request postgres-ai/database-lab!869
  • Loading branch information
NikolayS committed May 24, 2024
2 parents 12c0ed5 + b34d7d8 commit bb63d6c
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const Footer = () => {
const classes = useStyles()

return (
<div className={classes.footer}>
<footer className={classes.footer}>
<div className={classes.footerCopyrightItem}>
{new Date().getFullYear()} © Postgres.AI
</div>
Expand Down Expand Up @@ -103,6 +103,6 @@ export const Footer = () => {
</GatewayLink>
</div>
</div>
</div>
</footer>
)
}
14 changes: 11 additions & 3 deletions ui/packages/platform/src/pages/Bot/ChatsList/ChatsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import React from "react";
import { Link } from "react-router-dom";
import { useParams } from "react-router";
import cn from "classnames";
import { makeStyles, Theme } from "@material-ui/core";
import { makeStyles, Theme, useMediaQuery } from "@material-ui/core";
import Drawer from '@material-ui/core/Drawer';
import List from "@material-ui/core/List";
import Divider from "@material-ui/core/Divider";
Expand All @@ -18,6 +18,7 @@ import Box from "@mui/material/Box";
import { Spinner } from "@postgres.ai/shared/components/Spinner";
import { HeaderButtons, HeaderButtonsProps } from "../HeaderButtons/HeaderButtons";
import { BotMessage } from "../../../types/api/entities/bot";
import { theme } from "@postgres.ai/shared/styles/theme";


const useStyles = makeStyles<Theme, ChatsListProps>((theme) => ({
Expand Down Expand Up @@ -111,7 +112,7 @@ export const ChatsList = (props: ChatsListProps) => {
} = props;
const classes = useStyles(props);
const params = useParams<{ org?: string, threadId?: string }>();

const matches = useMediaQuery(theme.breakpoints.down('sm'));
const linkBuilder = (msgId: string) => {
if (params.org) {
return `/${params.org}/bot/${msgId}`
Expand All @@ -126,6 +127,12 @@ export const ChatsList = (props: ChatsListProps) => {
}
}

const handleCloseOnClickOutside = () => {
if (matches) {
onClose()
}
}

const loader = (
<Box className={classes.loader}>
<Spinner/>
Expand Down Expand Up @@ -172,11 +179,12 @@ export const ChatsList = (props: ChatsListProps) => {

return (
<Drawer
variant={'persistent'}
variant={matches ? 'temporary' : 'persistent'}
anchor="right"
BackdropProps={{ invisible: true }}
elevation={1}
open={isOpen}
onClose={handleCloseOnClickOutside}
classes={{
paper: classes.drawerPaper
}}
Expand Down
28 changes: 20 additions & 8 deletions ui/packages/platform/src/pages/Bot/Command/Command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { useBuffer } from './useBuffer'
import { useCaret } from './useCaret'
import { theme } from "@postgres.ai/shared/styles/theme";
import { isMobileDevice } from "../../../utils/utils";


type Props = {
Expand Down Expand Up @@ -76,7 +77,7 @@ export const Command = React.memo((props: Props) => {
const { sendDisabled, onSend, threadId } = props

const classes = useStyles()

const isMobile = isMobileDevice();
// Handle value.
const [value, setValue] = useState('')

Expand Down Expand Up @@ -104,11 +105,21 @@ export const Command = React.memo((props: Props) => {
}

const handleBlur = () => {
if (window.innerWidth < theme.breakpoints.values.sm)
window.scrollTo({
top: 0,
behavior: 'smooth'
})
if ((window.innerWidth < theme.breakpoints.values.sm) && isMobile) {
window.scrollTo({
top: 0,
behavior: 'smooth'
})
const footer: HTMLElement | null = document.querySelector("footer")
if (footer) footer.style.display = 'flex';
}
}

const handleFocus = () => {
if ((window.innerWidth < theme.breakpoints.values.sm) && isMobile) {
const footer: HTMLElement | null = document.querySelector("footer")
if (footer) footer.style.display = 'none';
}
}

const handleKeyDown = (e: React.KeyboardEvent) => {
Expand Down Expand Up @@ -163,19 +174,20 @@ export const Command = React.memo((props: Props) => {
return (
<div className={classes.root}>
<TextField
autoFocus={true}
autoFocus={window.innerWidth > theme.breakpoints.values.sm}
multiline
className={classes.field}
onKeyDown={handleKeyDown}
onChange={handleChange}
onBlur={handleBlur}
onFocus={handleFocus}
InputProps={{
inputRef,
classes: {
input: classes.fieldInput,
},
}}
value={value}
onChange={handleChange}
placeholder="Message..."
/>
<IconButton
Expand Down
18 changes: 14 additions & 4 deletions ui/packages/platform/src/pages/Bot/Command/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
export const checkIsSendCmd = (e: KeyboardEvent) =>
e.code === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey
import { isMobileDevice } from "../../../utils/utils";

export const checkIsNewLineCmd = (e: KeyboardEvent) =>
e.code === 'Enter' && (e.shiftKey || e.ctrlKey || e.metaKey)
export const checkIsSendCmd = (e: KeyboardEvent): boolean => {
if (isMobileDevice()) {
return false; // On mobile devices, Enter should not send.
}
return e.code === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey;
};

export const checkIsNewLineCmd = (e: KeyboardEvent): boolean => {
if (isMobileDevice()) {
return e.code === 'Enter'; // On mobile devices, Enter should create a new line.
}
return e.code === 'Enter' && (e.shiftKey || e.ctrlKey || e.metaKey);
};

export const addNewLine = (
value: string,
Expand Down
38 changes: 38 additions & 0 deletions ui/packages/platform/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,41 @@ export const validateDLEName = (name: string) => {
!name.match(/^([a-z](?:[-a-z0-9]{0,61}[a-z0-9])?|[1-9][0-9]{0,19})$/)
)
}

export const isMobileDevice = (): boolean => {
let hasTouchScreen = false;

// Check for modern touch screen devices using maxTouchPoints
if ("maxTouchPoints" in navigator) {
hasTouchScreen = navigator.maxTouchPoints > 0;
}
// Check for older versions of IE with msMaxTouchPoints
else if ("msMaxTouchPoints" in navigator) {
hasTouchScreen = (navigator as unknown as { msMaxTouchPoints: number }).msMaxTouchPoints > 0;
}
// Use matchMedia to check for coarse pointer devices
else {
const mQ = window.matchMedia("(pointer:coarse)");
if (mQ && mQ.media === "(pointer:coarse)") {
hasTouchScreen = mQ.matches;
}
// Check for the presence of the orientation property as a fallback (deprecated in modern browsers)
else if ('orientation' in window) {
hasTouchScreen = true;
}
// Last resort: fallback with user agent sniffing
else {
const UA = navigator.userAgent;
hasTouchScreen = (
/\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) ||
/\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA)
);
}
}

// Check for mobile screen width, 1366 because of iPad Pro in Landscape mode
// If this is not necessary, may reduce value to 1024 or 768
const isMobileScreen = window.innerWidth <= 1366;

return hasTouchScreen && isMobileScreen;
}
2 changes: 2 additions & 0 deletions ui/packages/shared/components/TextField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export type TextFieldProps = {
error?: boolean
placeholder?: string
onBlur?: TextFieldPropsBase['onBlur']
onFocus?: TextFieldPropsBase['onFocus']
}

const useStyles = makeStyles(
Expand Down Expand Up @@ -98,6 +99,7 @@ export const TextField = (props: TextFieldProps) => {
error={props.error}
placeholder={props.placeholder}
onBlur={props.onBlur}
onFocus={props.onFocus}
/>
)
}

0 comments on commit bb63d6c

Please sign in to comment.