Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENH] Made UI responsive for smaller screens #339

Merged
merged 18 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cypress/component/AuthDialog.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const props = {
onClose: () => {},
};

describe('ContinuousField', () => {
describe('AuthDialog', () => {
it('Displays a MUI dialog with the title and "sing in with google" button', () => {
cy.mount(
<GoogleOAuthProvider clientId="mock-client-id">
Expand Down
5 changes: 3 additions & 2 deletions cypress/component/ResultCard.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@ describe('ResultCard', () => {
cy.get('[data-cy="card-some uuid"]').should('contain', '5 subjects match / 10 total subjects');
cy.get('[data-cy="card-some uuid-checkbox"] input').should('be.checked');
cy.get('[data-cy="card-some uuid"] button')
.eq(1)
.should('contain', 'ASL')
.should('have.class', 'bg-zinc-800');
.should('have.css', 'background-color', 'rgb(113, 113, 122)');
cy.get('[data-cy="card-some uuid"] button')
.eq(2)
.should('contain', 'DWI')
.should('have.class', 'bg-red-700');
.should('have.css', 'background-color', 'rgb(205, 92, 92)');
rmanaem marked this conversation as resolved.
Show resolved Hide resolved

cy.get('[data-cy="card-some uuid-available-pipelines-button"]').trigger('mouseover', {
force: true,
Expand Down
18 changes: 18 additions & 0 deletions cypress/component/SmallScreenSizeDialog.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import SmallScreenSizeDialog from '../../src/components/SmallScreenSizeDialog';

const props = {
onClose: () => {},
};

describe('SmallScreenSizeDialog', () => {
it('Displays a MUI dialog with the title and "sing in with google" button', () => {
cy.mount(<SmallScreenSizeDialog open onClose={props.onClose} />);
cy.get('[data-cy="small-screen-size-dialog"]').should('be.visible');
cy.get('[data-cy="small-screen-size-dialog"]').should('contain', 'Unsupported Screen Size');
cy.get('[data-cy="small-screen-size-dialog"]').should(
'contain',
'not optimized for use on smaller screens'
);
cy.get('[data-cy="close-small-screen-size-dialog-button"]').should('be.visible');
});
});
9 changes: 8 additions & 1 deletion cypress/e2e/Alert.cy.ts → cypress/e2e/Feedback.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
nodeOptions,
} from '../fixtures/mocked-responses';

describe('Alert', () => {
describe('Feedback', () => {
it('Correctly displays and dismisses the alert', () => {
cy.intercept(
{
Expand Down Expand Up @@ -71,4 +71,11 @@ describe('Alert', () => {
cy.get('[data-cy="openneuro-alert"]').find('[data-testid="CloseIcon"]').click();
cy.get('[data-cy="openneuro-alert"]').should('not.exist');
});
it.only('Displays and closes small screen size dialog', () => {
cy.viewport(766, 500);
cy.visit('/');
cy.get('[data-cy="small-screen-size-dialog"]').should('be.visible');
cy.get('[data-cy="close-small-screen-size-dialog-button"]').click();
cy.get('[data-cy="small-screen-size-dialog"]').should('not.exist');
});
});
45 changes: 31 additions & 14 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import axios, { AxiosResponse } from 'axios';
import { Alert, Grow, IconButton } from '@mui/material';
import useMediaQuery from '@mui/material/useMediaQuery';
import CloseIcon from '@mui/icons-material/Close';
import { SnackbarKey, SnackbarProvider, closeSnackbar, enqueueSnackbar } from 'notistack';
import { jwtDecode } from 'jwt-decode';
Expand All @@ -24,9 +25,15 @@ import ResultContainer from './components/ResultContainer';
import Navbar from './components/Navbar';
import AuthDialog from './components/AuthDialog';
import ChatbotFeature from './components/Chatbot';
import SmallScreenSizeDialog from './components/SmallScreenSizeDialog';
import './App.css';
import logo from './assets/logo.png';

function App() {
// Screen is considered small if the width is less than 768px (according to tailwind docs)
const [isScreenSizeSmall, setIsScreenSizeSmall] = useState<boolean>(
useMediaQuery('(max-width: 767px)')
);
const [diagnosisOptions, setDiagnosisOptions] = useState<AttributeOption[]>([]);
const [assessmentOptions, setAssessmentOptions] = useState<AttributeOption[]>([]);
const [availableNodes, setAvailableNodes] = useState<NodeOption[]>([
Expand Down Expand Up @@ -427,15 +434,14 @@ function App() {

return (
<>
<div>
{enableAuth && (
<AuthDialog
open={openAuthDialog}
onAuth={(credential) => login(credential)}
onClose={() => setOpenAuthDialog(false)}
/>
)}
</div>
{enableAuth && (
<AuthDialog
open={openAuthDialog}
onAuth={(credential) => login(credential)}
onClose={() => setOpenAuthDialog(false)}
/>
)}
<SmallScreenSizeDialog open={isScreenSizeSmall} onClose={() => setIsScreenSizeSmall(false)} />
<SnackbarProvider
autoHideDuration={6000}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
Expand Down Expand Up @@ -476,10 +482,11 @@ function App() {
</>
)}

<div>{enableChatbot && <ChatbotFeature setResult={setResult} />}</div>
{enableChatbot && <ChatbotFeature setResult={setResult} />}

<div className="grid grid-cols-4 gap-4">
<div>
<div className="flex flex-wrap gap-3">
{/* 380px is currently the smallest width for the query form without dropdowns being affected */}
<div className="min-w-[380px] max-w-sm flex-1">
rmanaem marked this conversation as resolved.
Show resolved Hide resolved
<QueryForm
availableNodes={availableNodes}
diagnosisOptions={diagnosisOptions}
Expand Down Expand Up @@ -508,8 +515,18 @@ function App() {
onSubmitQuery={() => submitQuery()}
/>
</div>
<div className={loading ? 'col-span-3 animate-pulse' : 'col-span-3'}>
<ResultContainer response={sortedResults || null} />
<div
className={
loading
? 'flex flex-1 animate-pulse items-center justify-center'
: 'min-w-[600px] flex-1'
}
>
{loading ? (
<img src={logo} alt="Logo" className="max-h-20 animate-bounce" />
rmanaem marked this conversation as resolved.
Show resolved Hide resolved
) : (
<ResultContainer response={sortedResults || null} />
)}
</div>
</div>
</>
Expand Down
Binary file added src/assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 1 addition & 6 deletions src/components/AuthDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import DialogActions from '@mui/material/DialogActions';
import Button from '@mui/material/Button';
import useMediaQuery from '@mui/material/useMediaQuery';
import { useTheme } from '@mui/material/styles';
import { GoogleLogin } from '@react-oauth/google';

function AuthDialog({
Expand All @@ -16,11 +14,8 @@ function AuthDialog({
onAuth: (credential: string | undefined) => void;
onClose: () => void;
}) {
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));

return (
<Dialog fullScreen={fullScreen} open={open} onClose={onClose} data-cy="auth-dialog">
<Dialog open={open} onClose={onClose} data-cy="auth-dialog">
<DialogTitle>
You must log in to a trusted identity provider in order to query all available nodes!
</DialogTitle>
Expand Down
1 change: 0 additions & 1 deletion src/components/CategoricalField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ function CategoricalField({
{...params}
label={label}
placeholder="Select an option"
className="w-full"
/>
)}
multiple={multiple}
Expand Down
7 changes: 2 additions & 5 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Logout from '@mui/icons-material/Logout';
import Login from '@mui/icons-material/Login';
import Avatar from '@mui/material/Avatar';
import { enableAuth } from '../utils/constants';
import logo from '../assets/logo.png';

function Navbar({
isLoggedIn,
Expand Down Expand Up @@ -58,11 +59,7 @@ function Navbar({
<Toolbar className="my-4" data-cy="navbar">
<div className="flex w-full items-center justify-between">
<div className="flex items-center">
<img
src="https://raw.githubusercontent.com/neurobagel/documentation/main/docs/imgs/logo/neurobagel_logo.png"
alt="Logo"
height="60"
/>
rmanaem marked this conversation as resolved.
Show resolved Hide resolved
<img src={logo} alt="Logo" height="60" />
<div className="ml-4">
<Badge badgeContent={latestReleaseTag}>
<Typography variant="h5">Neurobagel Query</Typography>
Expand Down
36 changes: 18 additions & 18 deletions src/components/QueryForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ function QueryForm({
minNumImagingSessionsHelperText !== '';

return (
<div className="grid grid-cols-2 grid-rows-12 gap-2">
<div className="col-span-2">
<div className="flex flex-col gap-2">
<div>
<CategoricalField
label="Neurobagel graph"
options={availableNodes.map((n) => ({
Expand All @@ -105,28 +105,28 @@ function QueryForm({
inputValue={selectedNode}
/>
</div>
<div className="row-start-2">
<div>
<ContinuousField
helperText={minAgeExceedsMaxAge ? '' : minAgeHelperText}
label="Minimum age"
onFieldChange={updateContinuousQueryParams}
/>
</div>
<div className="row-start-2">
<div>
<ContinuousField
helperText={minAgeExceedsMaxAge ? '' : maxAgeHelperText}
label="Maximum age"
onFieldChange={updateContinuousQueryParams}
/>
</div>
{minAgeExceedsMaxAge && (
<div className="col-span-2">
<div>
<FormHelperText error>
Value of maximum age must be greater than or equal to value of minimum age
</FormHelperText>
</div>
)}
<div className="col-span-2">
<div>
<CategoricalField
label="Sex"
options={Object.entries(sexes).map(([key, value]) => ({
Expand All @@ -137,9 +137,9 @@ function QueryForm({
inputValue={sex}
/>
</div>
<div className="col-span-2 row-start-4">
<div className="grid grid-cols-12 items-center gap-4">
<div className="col-span-9">
<div>
<div className="flex flex-row items-center gap-3">
<div className="flex-1">
<CategoricalField
label="Diagnosis"
options={diagnosisOptions.map((d) => ({
Expand All @@ -151,7 +151,7 @@ function QueryForm({
disabled={isControl}
/>
</div>
<div>
<div className="flex-1">
<FormControlLabel
data-cy="healthy-control-checkbox"
control={<Checkbox name="healthyControl" />}
Expand All @@ -161,29 +161,29 @@ function QueryForm({
</div>
</div>
</div>
<div className="col-span-2 row-start-5">
<div>
<ContinuousField
helperText={minNumImagingSessionsHelperText}
label="Minimum number of imaging sessions"
onFieldChange={updateContinuousQueryParams}
/>
</div>
<div className="col-span-2 row-start-6">
<div>
<ContinuousField
helperText={minNumPhenotypicSessionsHelperText}
label="Minimum number of phenotypic sessions"
onFieldChange={updateContinuousQueryParams}
/>
</div>
<div className="col-span-2 row-start-7">
<div>
<CategoricalField
label="Assessment tool"
options={assessmentOptions.map((a) => ({ label: a.Label, id: a.TermURL }))}
onFieldChange={(label, value) => updateCategoricalQueryParams(label, value)}
inputValue={assessmentTool}
/>
</div>
<div className="col-span-2 row-start-8">
<div>
<CategoricalField
label="Imaging modality"
options={Object.entries(modalities).map(([, value]) => ({
Expand All @@ -194,7 +194,7 @@ function QueryForm({
inputValue={imagingModality}
/>
</div>
<div className="col-span-2 row-start-9">
<div>
<CategoricalField
label="Pipeline name"
options={Object.keys(pipelines).map((pipelineURI) => ({
Expand All @@ -211,7 +211,7 @@ function QueryForm({
title={<Typography variant="body1">Please select a pipeline name</Typography>}
placement="right"
>
<div className="col-span-2 row-start-10">
<div>
<CategoricalField
label="Pipeline version"
options={[]}
Expand All @@ -222,7 +222,7 @@ function QueryForm({
</div>
</Tooltip>
) : (
<div className="col-span-2 row-start-10">
<div>
<CategoricalField
label="Pipeline version"
options={Object.values(pipelines[(pipelineName as FieldInputOption).id]).map((v) => ({
Expand All @@ -235,7 +235,7 @@ function QueryForm({
</div>
)}

<div className={pipelineName ? 'col-span-2 row-start-11' : 'row-start-11'}>
<div>
<Button
data-cy="submit-query-button"
disabled={disableSubmit}
Expand Down
43 changes: 26 additions & 17 deletions src/components/ResultCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,24 @@ const ResultCard = memo(
}) => (
<Card data-cy={`card-${datasetUUID}`}>
<CardContent>
<div className="grid grid-cols-12 items-center gap-2">
<div className="col-end-1">
<Checkbox
data-cy={`card-${datasetUUID}-checkbox`}
checked={checked}
onChange={() => onCheckboxChange(datasetUUID)}
/>
<div className="grid grid-cols-3 items-center">
<div className="flex flex-row items-center">
<div>
<Checkbox
data-cy={`card-${datasetUUID}-checkbox`}
checked={checked}
onChange={() => onCheckboxChange(datasetUUID)}
/>
</div>
<div>
<Typography variant="h5">{datasetName}</Typography>
<Typography variant="subtitle1">from {nodeName}</Typography>
<Typography variant="subtitle2">
{numMatchingSubjects} subjects match / {datasetTotalSubjects} total subjects
</Typography>
</div>
</div>
<div className="col-span-6 col-start-1">
<Typography variant="h5">{datasetName}</Typography>
<Typography variant="subtitle1">from {nodeName}</Typography>
<Typography variant="subtitle2">
{numMatchingSubjects} subjects match / {datasetTotalSubjects} total subjects
</Typography>
</div>
<div className="col-span-2 col-start-7">
<div className="justify-self-center">
{Object.entries(pipelines).length === 0 ? (
<Button
data-cy={`card-${datasetUUID}-available-pipelines-button`}
Expand Down Expand Up @@ -89,13 +91,20 @@ const ResultCard = memo(
</Tooltip>
)}
</div>
<div className="col-span-2 col-start-11 justify-self-end">
<div className="justify-self-end">
<ButtonGroup>
{imageModals.sort().map((modal) => (
<Button
key={modal}
variant="contained"
className={`${modalities[modal].bgColor} shadow-none hover:bg-gray-400 hover:shadow-none`}
disableElevation
sx={{
backgroundColor: modalities[modal].bgColor,
'&:hover': {
backgroundColor: modalities[modal].bgColor,
cursor: 'default',
},
}}
>
{modalities[modal].name}
</Button>
Expand Down
Loading