Unverified Commit 52f70791 authored by Lyza Gardner's avatar Lyza Gardner Committed by GitHub

Merge pull request #1347 from hypothesis/fix-annotation-counts

Hide inaccurate annotation counts when in user-focused mode
parents 952443c1 c8b3aa87
'use strict'; 'use strict';
const { Fragment, createElement } = require('preact'); const { createElement } = require('preact');
const propTypes = require('prop-types'); const propTypes = require('prop-types');
const { useMemo } = require('preact/hooks'); const { useMemo } = require('preact/hooks');
...@@ -11,8 +11,6 @@ const useStore = require('../store/use-store'); ...@@ -11,8 +11,6 @@ const useStore = require('../store/use-store');
/** /**
* Of the annotations in the thread `annThread`, how many * Of the annotations in the thread `annThread`, how many
* are currently `visible` in the browser (sidebar)? * are currently `visible` in the browser (sidebar)?
*
* TODO: This function should be a selector or a reusable util
*/ */
const countVisibleAnns = annThread => { const countVisibleAnns = annThread => {
return annThread.children.reduce( return annThread.children.reduce(
...@@ -24,113 +22,164 @@ const countVisibleAnns = annThread => { ...@@ -24,113 +22,164 @@ const countVisibleAnns = annThread => {
}; };
/** /**
* A bar where the user can clear a selection or search and see whether * UI for displaying information about the currently-applied filtering of
* any search results were found. * annotations, and, in some cases, a mechanism for clearing the filter(s).
* */ * */
function SearchStatusBar({ rootThread }) { function SearchStatusBar({ rootThread }) {
const thread = useStore(store => rootThread.thread(store.getRootState()));
const actions = useStore(store => ({ const actions = useStore(store => ({
clearSelection: store.clearSelection, clearSelection: store.clearSelection,
})); }));
const storeState = useStore(store => ({ const counts = useStore(store => ({
annotationCount: store.annotationCount(), annotations: store.annotationCount(),
notes: store.noteCount(),
}));
const {
directLinkedGroupFetchFailed,
filterQuery,
focusModeFocused,
focusModeUserPrettyName,
selectionMap,
selectedTab,
} = useStore(store => ({
directLinkedGroupFetchFailed: store.getRootState().directLinked directLinkedGroupFetchFailed: store.getRootState().directLinked
.directLinkedGroupFetchFailed, .directLinkedGroupFetchFailed,
filterQuery: store.getRootState().selection.filterQuery, filterQuery: store.getRootState().selection.filterQuery,
focusModeFocused: store.focusModeFocused(), focusModeFocused: store.focusModeFocused(),
focusModeUserPrettyName: store.focusModeUserPrettyName(), focusModeUserPrettyName: store.focusModeUserPrettyName(),
noteCount: store.noteCount(), selectionMap: store.getRootState().selection.selectedAnnotationMap,
selectedAnnotationMap: store.getRootState().selection.selectedAnnotationMap,
selectedTab: store.getRootState().selection.selectedTab, selectedTab: store.getRootState().selection.selectedTab,
})); }));
const filterActive = !!storeState.filterQuery; // The search status bar UI represents multiple "modes" of filtering
const modes = {
const thread = useStore(store => rootThread.thread(store.getRootState())); /**
* @type {Boolean}
* A search (filter) query, visible to the user in the search bar, is
* currently applied
*/
filtered: !!filterQuery,
/**
* @type {Boolean}
* The client has a currently-applied focus on a single user. Superseded by
* `filtered` mode.
*/
focused: focusModeFocused && !filterQuery,
/**
* @type {Boolean}
* 0 - n annotations are currently "selected", by, e.g. clicking on highlighted
* text in the host page, direct-linking to an annotation, etc. Superseded by
* `filtered` mode.
*/
selected: (() => {
if (directLinkedGroupFetchFailed) {
return true;
}
return (
!!selectionMap && Object.keys(selectionMap).length > 0 && !filterQuery
);
})(),
};
const visibleCount = useMemo(() => { const visibleCount = useMemo(() => {
return countVisibleAnns(thread); return countVisibleAnns(thread);
}, [thread]); }, [thread]);
const filterResults = (() => { // Each "mode" has corresponding descriptive text about the number of
switch (visibleCount) { // matching/applicable annotations and, sometimes, a way to clear the
case 0: // filter
return `No results for "${storeState.filterQuery}"`; const modeText = {
case 1: filtered: (() => {
return '1 search result'; switch (visibleCount) {
default: case 0:
return `${visibleCount} search results`; return `No results for "${filterQuery}"`;
} case 1:
})(); return '1 search result';
default:
const focusResults = (() => { return `${visibleCount} search results`;
switch (visibleCount) { }
case 0: })(),
return `No annotations for ${storeState.focusModeUserPrettyName}`; focused: (() => {
case 1: switch (visibleCount) {
return 'Showing 1 annotation'; case 0:
default: return `No annotations for ${focusModeUserPrettyName}`;
return `Showing ${visibleCount} annotations`; case 1:
} return 'Showing 1 annotation';
})(); default:
return `Showing ${visibleCount} annotations`;
}
})(),
selected: (() => {
// Generate the proper text to show on the clear-selection button.
// For non-user-focused modes, we can display the number of annotations
// that will be visible if the selection is cleared (`counts.annotations`)
// but this number is inaccurate/misleading when also focused on a user.
let selectedText;
switch (selectedTab) {
case uiConstants.TAB_ORPHANS:
selectedText = 'Show all annotations and notes';
break;
case uiConstants.TAB_NOTES:
selectedText = 'Show all notes';
if (counts.notes > 1 && !modes.focused) {
selectedText += ` (${counts.notes})`;
} else if (modes.focused) {
selectedText += ` by ${focusModeUserPrettyName}`;
}
break;
case uiConstants.TAB_ANNOTATIONS:
selectedText = 'Show all annotations';
if (counts.annotations > 1 && !modes.focused) {
selectedText += ` (${counts.annotations})`;
} else if (modes.focused) {
selectedText = `Show all annotations by ${focusModeUserPrettyName}`;
}
break;
}
return selectedText;
})(),
};
const areNotAllAnnotationsVisible = () => { const btnProps = {
if (storeState.directLinkedGroupFetchFailed) { className: 'primary-action-btn primary-action-btn--short',
return true; onClick: actions.clearSelection,
}
const selection = storeState.selectedAnnotationMap;
if (!selection) {
return false;
}
return Object.keys(selection).length > 0;
}; };
return ( return (
<div> <div>
{filterActive && ( {modes.filtered && (
<div className="search-status-bar"> <div className="search-status-bar">
<button <button
className="primary-action-btn primary-action-btn--short"
onClick={actions.clearSelection}
title="Clear the search filter and show all annotations" title="Clear the search filter and show all annotations"
{...btnProps}
> >
<i className="primary-action-btn__icon h-icon-close" /> <i className="primary-action-btn__icon h-icon-close" />
Clear search Clear search
</button> </button>
<span>{filterResults}</span> <span className="search-status-bar__filtered-text">
{modeText.filtered}
</span>
</div> </div>
)} )}
{!filterActive && storeState.focusModeFocused && ( {modes.focused && (
<div className="search-status-bar"> <div className="search-status-bar">
<strong>{focusResults}</strong> <span className="search-status-bar__focused-text">
<strong>{modeText.focused}</strong>
</span>
</div> </div>
)} )}
{!filterActive && areNotAllAnnotationsVisible() && ( {modes.selected && (
<div className="search-status-bar"> <div className="search-status-bar">
<button <button
className="primary-action-btn primary-action-btn--short"
onClick={actions.clearSelection}
title="Clear the selection and show all annotations" title="Clear the selection and show all annotations"
{...btnProps}
> >
{storeState.selectedTab === uiConstants.TAB_ORPHANS && ( <span className="search-status-bar__selected-text">
<Fragment>Show all annotations and notes</Fragment> {modeText.selected}
)} </span>
{storeState.selectedTab === uiConstants.TAB_ANNOTATIONS && (
<Fragment>
Show all annotations
{storeState.annotationCount > 1 && (
<span> ({storeState.annotationCount})</span>
)}
</Fragment>
)}
{storeState.selectedTab === uiConstants.TAB_NOTES && (
<Fragment>
Show all notes
{storeState.noteCount > 1 && (
<span> ({storeState.noteCount})</span>
)}
</Fragment>
)}
</button> </button>
</div> </div>
)} )}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment