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

🏷 Accessibility and Usability Improvements on Label Tab #1014

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
cc8d77b
aria: hide leafletview from tree & label as "map"
JGreenlee Aug 11, 2023
5ef98cb
replace non-clickable <IconButton>s with <Icon>s
JGreenlee Aug 11, 2023
29d951d
wrap LabelDetailsScreen in <Modal>
JGreenlee Aug 11, 2023
1a0abae
aria: add labels, roles to DiaryCard components
JGreenlee Aug 11, 2023
5970f89
aria: add accessibilityLabels to Header components
JGreenlee Aug 11, 2023
e24989f
create EnketoModal.tsx
JGreenlee Aug 14, 2023
7e04637
finish implementation of EnketoModal
JGreenlee Aug 15, 2023
4341f4c
logger.js -> logger.ts
JGreenlee Aug 15, 2023
55cf019
add functions to logger.ts + use in EnketoModal
JGreenlee Aug 15, 2023
3745535
add more levels to logger, also add types
JGreenlee Aug 15, 2023
e084b59
add caching for loading surveys
JGreenlee Aug 15, 2023
fd1ee53
remove ReactHello.jsx
JGreenlee Aug 15, 2023
b48196b
tweak enketo styles for new EnketoModal
JGreenlee Aug 15, 2023
4cf9f58
Icon refactor, support other IconButton props too
JGreenlee Aug 15, 2023
4796626
LabelDetailsScreen: UI fixes, accessibility fixes
JGreenlee Aug 15, 2023
aaf6c06
use Dialogs for multilabel popups
JGreenlee Aug 15, 2023
a72fd4f
EnketoModal: tweak "Dismiss" and "Save" buttons
JGreenlee Aug 15, 2023
473bf6c
survey.js -> survey.ts
JGreenlee Aug 16, 2023
6b6db8b
refactor SurveyOptions to TypeScript
JGreenlee Aug 16, 2023
d81ba67
EnketoModal: receive click events on <button>s
JGreenlee Aug 16, 2023
0b59436
diary.js don't depend 'emission.survey'
JGreenlee Aug 16, 2023
81b882f
Merge branch 'framework_migration_prep' into label-improvements-aug-2023
JGreenlee Aug 16, 2023
e1c13a3
UserInputButton: use new EnketoModal
JGreenlee Aug 16, 2023
efb9e50
EnketoModal: make survey take up full height
JGreenlee Aug 16, 2023
12f81b2
AdedNotesList: use new EnketoModal
JGreenlee Aug 16, 2023
686cf4e
convert "confirm edit" popup to React Native Modal
JGreenlee Aug 16, 2023
65637a6
TimestampBadge.jsx -> TimestampBadge.tsx
JGreenlee Aug 16, 2023
4e09b5b
switch to TS typings on non-angularized components
JGreenlee Aug 16, 2023
011d9ba
bump react-native-web to ^0.19
JGreenlee Aug 18, 2023
cc991e6
create new confirmHelper.ts
JGreenlee Aug 18, 2023
8f6c778
use confirmHelper instead of Angular ConfirmHelper
JGreenlee Aug 18, 2023
da3d27c
remove trip-confirm-services.js
JGreenlee Aug 18, 2023
1b5e519
adjust DiaryButton UI
JGreenlee Aug 18, 2023
aab2c13
fix ui_config references in multi-label-ui
JGreenlee Aug 18, 2023
83cd604
fix translations
JGreenlee Aug 18, 2023
7f2bed8
try locally hosted configs on __DEV__, & refactor
JGreenlee Aug 18, 2023
76c4154
DiaryHelper: remove getNominatimLocName
JGreenlee Aug 20, 2023
8f75fe0
finish rewriting DiaryHelper
JGreenlee Aug 20, 2023
6a654ae
implement useDerivedProperties
JGreenlee Aug 20, 2023
364d1c4
unify imperial miles/km functions
JGreenlee Aug 20, 2023
33f7758
refactor into StartEndLocations.tsx
JGreenlee Aug 20, 2023
f1a905a
give unloaded address names default value ''
JGreenlee Aug 20, 2023
edd4ccf
make draft colors grey, not green
JGreenlee Aug 20, 2023
616a439
on load config, cache resources referenced by URL
JGreenlee Aug 20, 2023
1cf7e42
commit diaryTypes.ts [WIP]
JGreenlee Aug 20, 2023
e4f3af4
DiaryButton truncate overflow with ellipsis
JGreenlee Aug 21, 2023
423dbf6
fix dynamic config loading in dev environments
JGreenlee Aug 21, 2023
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 package.cordovabuild.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@
"react-native-safe-area-context": "^4.6.3",
"react-native-screens": "^3.22.0",
"react-native-vector-icons": "^9.2.0",
"react-native-web": "^0.18.10",
"react-native-web": "^0.19.7",
"react-native-web-webview": "^1.0.2",
"react-qr-code": "^2.0.11",
"shelljs": "^0.8.5"
Expand Down
2 changes: 1 addition & 1 deletion package.serve.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
"react-native-safe-area-context": "^4.6.3",
"react-native-screens": "^3.22.0",
"react-native-vector-icons": "^9.2.0",
"react-native-web": "^0.18.10",
"react-native-web": "^0.19.7",
"react-native-web-webview": "^1.0.2",
"react-qr-code": "^2.0.11",
"shelljs": "^0.8.5"
Expand Down
23 changes: 10 additions & 13 deletions www/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
/* if we don't contain them here, they will leak into the rest of the app */
.enketo-plugin {
@import 'enketo-core/src/sass/formhub/formhub.scss';
flex: 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine for this release, which I really don't want to make other changes to, but let's test out whether turning off pagination deals with autoscrolling properly and then turn off pagination by default. I think that is what we decided.

I think we will still need this - what if there is only one "yes/no" question - but we might be able to remove it then.

Copy link
Collaborator Author

@JGreenlee JGreenlee Aug 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pagination being on or off isn't something we control from the phone code; it's actually an option in the forms and can be enabled/disabled from KoboToolbox or the Excel file or the XML file. When I brought up the topic, I was just suggesting that we change the built-in demographic survey to not use pagination.

But if someone else wants to supply their own surveys that do have pagination turned on, they can do that as much as they please.

The mock Time-Use survey we have been using does not use pagination and the scrolling has been fine.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I was suggesting that we turn pagination off for the default survey.
I know that the mock time use is not paginated, but it is also not very long.
I want to try it with a long survey, like the demographics, and see if the experience is smooth - e.g. does it automatically scroll down when a question is complete, and whether that feels weird or not?

.question.non-select {
display: inline-block;
}
.question input[name*="_date"],
.question input[name*="_time"] {
width: calc(40vw - 10px);
margin-right: 5px;
display: flex;
}
}

.enketo-plugin .form-header {
Expand Down Expand Up @@ -1471,16 +1481,3 @@ svg {
width: 3ch;
left: calc(8px + 2.5ch);
}

.enketo-plugin .question.inline-datetime > input[name*="_date"],
.enketo-plugin .question.inline-datetime > input[name*="_time"] {
display: flex;
width: auto;
min-width: 65%;
max-width: 85%;
}

.enketo-plugin .question.inline-datetime {
display: inline-grid;
width: 50%;
}
4 changes: 1 addition & 3 deletions www/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@ import './js/services.js';
import './js/i18n-utils.js';
import './js/intro.js';
import './js/main.js';
import './js/survey/survey.js';
import './js/survey/input-matcher.js';
import './js/survey/multilabel/infinite_scroll_filters.js';
import './js/survey/multilabel/trip-confirm-services.js';
import './js/survey/multilabel/multi-label-ui.js';
import './js/diary.js';
import './js/recent.js';
Expand All @@ -50,6 +48,6 @@ import './js/control/collect-settings.js';
import './js/control/sync-settings.js';
import './js/metrics-factory.js';
import './js/metrics-mappings.js';
import './js/plugin/logger.js';
import './js/plugin/logger.ts';
import './js/plugin/storage.js';
import './js/appstatus/permissioncheck.js';
93 changes: 0 additions & 93 deletions www/js/ReactHello.jsx

This file was deleted.

10 changes: 5 additions & 5 deletions www/js/appTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ const flavorOverrides = {
},
}
},
draft: { // for draft TripCards; a greenish color scheme
draft: { // for draft TripCards; a greyish color scheme
colors: {
primary: '#637d6a', // lch(50 15 150)
primaryContainer: '#b8cbbd', // lch(80 10 150)
primary: '#616971', // lch(44 6 250)
primaryContainer: '#b6bcc2', // lch(76 4 250)
elevation: {
level1: '#e1e3e1', // lch(90 1 150)
level2: '#d7dbd8', // lch(87 2 150)
level1: '#dbddde', // lch(88 1 250)
level2: '#d2d5d8', // lch(85 2 250)
},
}
},
Expand Down
19 changes: 19 additions & 0 deletions www/js/commHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { logDebug } from "./plugin/logger";

/**
* @param url URL endpoint for the request
* @returns Promise of the fetched response (as text) or cached text from local storage
*/
export async function fetchUrlCached(url) {
const stored = localStorage.getItem(url);
Copy link
Contributor

@shankari shankari Aug 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future: I think you can theoretically reuse this to cache the nomimatim reverse-lookup as well although the key length may be too long and it is not that important going forward anyway.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nominatim lookup is also cached using localstorage, but in a different way so that we can subscribe to its updates.
(for the key, I used the actual coordinates)

Both implementations are temporary anyway because localstorage is not a good long-term solution and we need to find an alternative eventually (it depends on what we are allowed to use)

if (stored) {
logDebug(`fetchUrlCached: found cached data for url ${url}, returning`);
return Promise.resolve(stored);
}
logDebug(`fetchUrlCached: found no cached data for url ${url}, fetching`);
const response = await fetch(url);
const text = await response.text();
localStorage.setItem(url, text);
logDebug(`fetchUrlCached: fetched data for url ${url}, returning`);
return text;
}
26 changes: 26 additions & 0 deletions www/js/components/Icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* React Native Paper provides an IconButton component, but it doesn't provide a plain Icon.
We want a plain Icon that is 'presentational' - not seen as interactive to the user or screen readers, and
it should not have any extra padding or margins around it. */
/* Using the underlying Icon from React Native Paper doesn't bundle correctly, so the easiest thing to do
for now is wrap an IconButton and remove its interactivity and padding. */

import React from 'react';
import { StyleSheet } from 'react-native';
import { IconButton } from 'react-native-paper';
import { Props as IconButtonProps } from 'react-native-paper/lib/typescript/src/components/IconButton/IconButton'

export const Icon = ({style, ...rest}: IconButtonProps) => {
return (
<IconButton style={[s.icon, style]} {...rest}
role='none' focusable={false} accessibilityHidden={true} />
);
}

const s = StyleSheet.create({
icon: {
width: 'unset',
height: 'unset',
padding: 0,
margin: 0,
},
});
4 changes: 2 additions & 2 deletions www/js/components/LeafletView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const LeafletView = ({ geojson, opts, ...otherProps }) => {
const mapElId = `map-${geojson.data.id.replace(/[^a-zA-Z0-9]/g, '')}`;

return (
<View {...otherProps}>
<View {...otherProps} accessibilityRole='img' aria-label='Map'>
<style>{`
.leaflet-bottom {
max-width: 100%;
Expand Down Expand Up @@ -87,7 +87,7 @@ const LeafletView = ({ geojson, opts, ...otherProps }) => {
content: "󰈻"; ${/* glyph for 'flag' from https://pictogrammers.com/library/mdi/icon/flag/ */''}
}
`}</style>
<div id={mapElId} ref={mapElRef} data-tap-disabled="true"
<div id={mapElId} ref={mapElRef} data-tap-disabled="true" aria-hidden={true}
style={{width: '100%', height: '100%', zIndex: 0}}></div>
</View>
);
Expand Down
11 changes: 6 additions & 5 deletions www/js/components/NavBarButton.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from "react";
import { View, StyleSheet } from "react-native";
import color from "color";
import { Button, IconButton, useTheme } from "react-native-paper";
import { Button, useTheme } from "react-native-paper";
import { Icon } from "./Icon";

const NavBarButton = ({ children, icon, onPressAction }) => {
const NavBarButton = ({ children, icon, onPressAction, ...otherProps }) => {

const { colors } = useTheme();
const buttonColor = color(colors.onBackground).alpha(.07).rgb().string();
Expand All @@ -13,14 +14,14 @@ const NavBarButton = ({ children, icon, onPressAction }) => {
<Button mode="outlined" buttonColor={buttonColor} textColor={colors.onBackground}
contentStyle={{ flexDirection: 'row', height: 36 }}
style={[s.btn, {borderColor: outlineColor}]}
labelStyle={s.label} onPress={() => onPressAction()}>
labelStyle={s.label} onPress={() => onPressAction()}
{...otherProps}>
<View style={s.textWrapper}>
{children}
</View>
{icon &&
<View>
<IconButton icon={icon} color={colors.onBackground} size={20}
style={s.icon} />
<Icon icon={icon} color={colors.onBackground} size={20} style={{marginVertical: 'auto'}}/>
</View>
}
</Button>
Expand Down
74 changes: 58 additions & 16 deletions www/js/config/dynamic_config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
'use strict';

import angular from 'angular';
import { displayError, logDebug } from '../plugin/logger';
import i18next from 'i18next';
import { fetchUrlCached } from '../commHelper';

angular.module('emission.config.dynamic', ['emission.plugin.logger',
'emission.plugin.kvstore'])
Expand Down Expand Up @@ -43,23 +46,60 @@ angular.module('emission.config.dynamic', ['emission.plugin.logger',
}
}

var readConfigFromServer = function(label) {
Logger.log("Received request to join "+label);
// The URL prefix from which config files will be downloaded and read.
// Change this if you supply your own config files.
const downloadURL = "https://raw.githubusercontent.com/e-mission/nrel-openpath-deploy-configs/main/configs/"+label+".nrel-op.json"
Logger.log("Downloading data from "+downloadURL);
return $http.get(downloadURL).then((result) => {
Logger.log("Successfully found the "+downloadURL+", result is " + JSON.stringify(result.data).substring(0,10));
const parsedConfig = result.data;
const connectionURL = parsedConfig.server? parsedConfig.server.connectUrl : "dev defaults";
_fillStudyName(parsedConfig);
_backwardsCompatSurveyFill(parsedConfig);
Logger.log("Successfully downloaded config with version "+parsedConfig.version
+" for "+parsedConfig.intro.translated_text.en.deployment_name
+" and data collection URL "+connectionURL);
return parsedConfig;
/* Fetch and cache any surveys resources that are referenced by URL in the config,
as well as the label_options config if it is present.
This way they will be available when the user needs them, and we won't have to
fetch them again unless local storage is cleared. */
const cacheResourcesFromConfig = (config) => {
if (config.survey_info?.surveys) {
Object.values(config.survey_info.surveys).forEach((survey) => {
if (!survey?.formPath)
throw new Error('while fetching resources in config, survey_info.surveys has a survey without a formPath');
JGreenlee marked this conversation as resolved.
Show resolved Hide resolved
fetchUrlCached(survey.formPath);
});
}
if (config.label_options) {
fetchUrlCached(config.label_options);
}
}

const readConfigFromServer = async (label) => {
const config = await fetchConfig(label);
Logger.log("Successfully found config, result is " + JSON.stringify(config).substring(0, 10));

// fetch + cache any resources referenced in the config, but don't 'await' them so we don't block
// the config loading process
cacheResourcesFromConfig(config);

const connectionURL = config.server ? config.server.connectUrl : "dev defaults";
_fillStudyName(config);
_backwardsCompatSurveyFill(config);
Logger.log("Successfully downloaded config with version " + config.version
+ " for " + config.intro.translated_text.en.deployment_name
+ " and data collection URL " + connectionURL);
return config;
}

const fetchConfig = async (label, alreadyTriedLocal=false) => {
Logger.log("Received request to join "+label);
const downloadURL = `https://raw.githubusercontent.com/e-mission/nrel-openpath-deploy-configs/main/configs/${label}.nrel-op.json`;
if (!__DEV__ || alreadyTriedLocal) {
Logger.log("Fetching config from github");
const r = await fetch(downloadURL);
if (!r.ok) throw new Error('Unable to fetch config from github');
return r.json();
}
else {
Logger.log("Running in dev environment, checking for locally hosted config");
try {
const r = await fetch('http://localhost:9090/configs/'+label+'.nrel-op.json');
if (!r.ok) throw new Error('Local config not found');
return r.json();
} catch (err) {
Logger.log("Local config not found");
return fetchConfig(label, true);
}
}
}

dc.loadSavedConfig = function() {
Expand Down Expand Up @@ -139,6 +179,8 @@ angular.module('emission.config.dynamic', ['emission.plugin.logger',
return true;
}).catch((storeError) =>
Logger.displayError(i18next.t('config.unable-to-store-config'), storeError));
}).catch((fetchErr) => {
displayError(fetchErr, i18next.t('config.unable-download-config'));
});
}

Expand Down
Loading