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';
const { Fragment, createElement } = require('preact');
const { createElement } = require('preact');
const propTypes = require('prop-types');
const { useMemo } = require('preact/hooks');
......@@ -11,8 +11,6 @@ const useStore = require('../store/use-store');
/**
* Of the annotations in the thread `annThread`, how many
* are currently `visible` in the browser (sidebar)?
*
* TODO: This function should be a selector or a reusable util
*/
const countVisibleAnns = annThread => {
return annThread.children.reduce(
......@@ -24,113 +22,164 @@ const countVisibleAnns = annThread => {
};
/**
* A bar where the user can clear a selection or search and see whether
* any search results were found.
* UI for displaying information about the currently-applied filtering of
* annotations, and, in some cases, a mechanism for clearing the filter(s).
* */
function SearchStatusBar({ rootThread }) {
const thread = useStore(store => rootThread.thread(store.getRootState()));
const actions = useStore(store => ({
clearSelection: store.clearSelection,
}));
const storeState = useStore(store => ({
annotationCount: store.annotationCount(),
const counts = useStore(store => ({
annotations: store.annotationCount(),
notes: store.noteCount(),
}));
const {
directLinkedGroupFetchFailed,
filterQuery,
focusModeFocused,
focusModeUserPrettyName,
selectionMap,
selectedTab,
} = useStore(store => ({
directLinkedGroupFetchFailed: store.getRootState().directLinked
.directLinkedGroupFetchFailed,
filterQuery: store.getRootState().selection.filterQuery,
focusModeFocused: store.focusModeFocused(),
focusModeUserPrettyName: store.focusModeUserPrettyName(),
noteCount: store.noteCount(),
selectedAnnotationMap: store.getRootState().selection.selectedAnnotationMap,
selectionMap: store.getRootState().selection.selectedAnnotationMap,
selectedTab: store.getRootState().selection.selectedTab,
}));
const filterActive = !!storeState.filterQuery;
const thread = useStore(store => rootThread.thread(store.getRootState()));
// The search status bar UI represents multiple "modes" of filtering
const modes = {
/**
* @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(() => {
return countVisibleAnns(thread);
}, [thread]);
const filterResults = (() => {
switch (visibleCount) {
case 0:
return `No results for "${storeState.filterQuery}"`;
case 1:
return '1 search result';
default:
return `${visibleCount} search results`;
}
})();
const focusResults = (() => {
switch (visibleCount) {
case 0:
return `No annotations for ${storeState.focusModeUserPrettyName}`;
case 1:
return 'Showing 1 annotation';
default:
return `Showing ${visibleCount} annotations`;
}
})();
// Each "mode" has corresponding descriptive text about the number of
// matching/applicable annotations and, sometimes, a way to clear the
// filter
const modeText = {
filtered: (() => {
switch (visibleCount) {
case 0:
return `No results for "${filterQuery}"`;
case 1:
return '1 search result';
default:
return `${visibleCount} search results`;
}
})(),
focused: (() => {
switch (visibleCount) {
case 0:
return `No annotations for ${focusModeUserPrettyName}`;
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 = () => {
if (storeState.directLinkedGroupFetchFailed) {
return true;
}
const selection = storeState.selectedAnnotationMap;
if (!selection) {
return false;
}
return Object.keys(selection).length > 0;
const btnProps = {
className: 'primary-action-btn primary-action-btn--short',
onClick: actions.clearSelection,
};
return (
<div>
{filterActive && (
{modes.filtered && (
<div className="search-status-bar">
<button
className="primary-action-btn primary-action-btn--short"
onClick={actions.clearSelection}
title="Clear the search filter and show all annotations"
{...btnProps}
>
<i className="primary-action-btn__icon h-icon-close" />
Clear search
</button>
<span>{filterResults}</span>
<span className="search-status-bar__filtered-text">
{modeText.filtered}
</span>
</div>
)}
{!filterActive && storeState.focusModeFocused && (
{modes.focused && (
<div className="search-status-bar">
<strong>{focusResults}</strong>
<span className="search-status-bar__focused-text">
<strong>{modeText.focused}</strong>
</span>
</div>
)}
{!filterActive && areNotAllAnnotationsVisible() && (
{modes.selected && (
<div className="search-status-bar">
<button
className="primary-action-btn primary-action-btn--short"
onClick={actions.clearSelection}
title="Clear the selection and show all annotations"
{...btnProps}
>
{storeState.selectedTab === uiConstants.TAB_ORPHANS && (
<Fragment>Show all annotations and notes</Fragment>
)}
{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>
)}
<span className="search-status-bar__selected-text">
{modeText.selected}
</span>
</button>
</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