Commit e0f3f7d3 authored by Robert Knight's avatar Robert Knight

Split `useStore` hook into general and app-specific hooks

Split the `useStoreProxy` hook into a generic/base `useStore` hook that handles
wrapping a store created with `createStore`, and a `useSidebarStore` hook that
handles looking up the sidebar's main store via `useService('store')` and
passing it to the base hook.

This follows the existing separation of responsibilities between `createStore`
vs `createSidebarStore`, and would potentially allow us to split the generic
store infrastructure code into a separate package for use in other projects in
future.
parent 089a2702
import { Actions, Spinner } from '@hypothesis/frontend-shared'; import { Actions, Spinner } from '@hypothesis/frontend-shared';
import classnames from 'classnames'; import classnames from 'classnames';
import { useStoreProxy } from '../../store/use-store'; import { useSidebarStore } from '../../store';
import { isOrphan, isSaved, quote } from '../../helpers/annotation-metadata'; import { isOrphan, isSaved, quote } from '../../helpers/annotation-metadata';
import { withServices } from '../../service-context'; import { withServices } from '../../service-context';
...@@ -72,7 +72,7 @@ function Annotation({ ...@@ -72,7 +72,7 @@ function Annotation({
}) { }) {
const isCollapsedReply = isReply && threadIsCollapsed; const isCollapsedReply = isReply && threadIsCollapsed;
const store = useStoreProxy(); const store = useSidebarStore();
const draft = annotation && store.getDraft(annotation); const draft = annotation && store.getDraft(annotation);
......
...@@ -8,7 +8,7 @@ import { ...@@ -8,7 +8,7 @@ import {
} from '../../helpers/annotation-sharing'; } from '../../helpers/annotation-sharing';
import { isPrivate, permits } from '../../helpers/permissions'; import { isPrivate, permits } from '../../helpers/permissions';
import { withServices } from '../../service-context'; import { withServices } from '../../service-context';
import { useStoreProxy } from '../../store/use-store'; import { useSidebarStore } from '../../store';
import AnnotationShareControl from './AnnotationShareControl'; import AnnotationShareControl from './AnnotationShareControl';
...@@ -48,7 +48,7 @@ function AnnotationActionBar({ ...@@ -48,7 +48,7 @@ function AnnotationActionBar({
settings, settings,
toastMessenger, toastMessenger,
}) { }) {
const store = useStoreProxy(); const store = useSidebarStore();
const userProfile = store.profile(); const userProfile = store.profile();
const annotationGroup = store.getGroup(annotation.group); const annotationGroup = store.getGroup(annotation.group);
const isLoggedIn = store.isLoggedIn(); const isLoggedIn = store.isLoggedIn();
......
...@@ -2,7 +2,7 @@ import { Icon, LabeledButton } from '@hypothesis/frontend-shared'; ...@@ -2,7 +2,7 @@ import { Icon, LabeledButton } from '@hypothesis/frontend-shared';
import classnames from 'classnames'; import classnames from 'classnames';
import { useMemo, useState } from 'preact/hooks'; import { useMemo, useState } from 'preact/hooks';
import { useStoreProxy } from '../../store/use-store'; import { useSidebarStore } from '../../store';
import { isThirdPartyUser } from '../../helpers/account-id'; import { isThirdPartyUser } from '../../helpers/account-id';
import { isHidden } from '../../helpers/annotation-metadata'; import { isHidden } from '../../helpers/annotation-metadata';
import { withServices } from '../../service-context'; import { withServices } from '../../service-context';
...@@ -71,7 +71,7 @@ function AnnotationBody({ annotation, settings }) { ...@@ -71,7 +71,7 @@ function AnnotationBody({ annotation, settings }) {
// collapsing/expanding is relevant? // collapsing/expanding is relevant?
const [collapsible, setCollapsible] = useState(false); const [collapsible, setCollapsible] = useState(false);
const store = useStoreProxy(); const store = useSidebarStore();
const defaultAuthority = store.defaultAuthority(); const defaultAuthority = store.defaultAuthority();
const draft = store.getDraft(annotation); const draft = store.getDraft(annotation);
......
...@@ -4,7 +4,7 @@ import { useCallback, useState } from 'preact/hooks'; ...@@ -4,7 +4,7 @@ import { useCallback, useState } from 'preact/hooks';
import { withServices } from '../../service-context'; import { withServices } from '../../service-context';
import { isReply, isSaved } from '../../helpers/annotation-metadata'; import { isReply, isSaved } from '../../helpers/annotation-metadata';
import { applyTheme } from '../../helpers/theme'; import { applyTheme } from '../../helpers/theme';
import { useStoreProxy } from '../../store/use-store'; import { useSidebarStore } from '../../store';
import MarkdownEditor from '../MarkdownEditor'; import MarkdownEditor from '../MarkdownEditor';
import TagEditor from '../TagEditor'; import TagEditor from '../TagEditor';
...@@ -46,7 +46,7 @@ function AnnotationEditor({ ...@@ -46,7 +46,7 @@ function AnnotationEditor({
/** @type {string|null} */ (null) /** @type {string|null} */ (null)
); );
const store = useStoreProxy(); const store = useSidebarStore();
const group = store.getGroup(annotation.group); const group = store.getGroup(annotation.group);
const shouldShowLicense = const shouldShowLicense =
......
...@@ -2,7 +2,7 @@ import { Icon, LinkButton } from '@hypothesis/frontend-shared'; ...@@ -2,7 +2,7 @@ import { Icon, LinkButton } from '@hypothesis/frontend-shared';
import { useMemo } from 'preact/hooks'; import { useMemo } from 'preact/hooks';
import { withServices } from '../../service-context'; import { withServices } from '../../service-context';
import { useStoreProxy } from '../../store/use-store'; import { useSidebarStore } from '../../store';
import { isThirdPartyUser, username } from '../../helpers/account-id'; import { isThirdPartyUser, username } from '../../helpers/account-id';
import { import {
domainAndTitle, domainAndTitle,
...@@ -56,7 +56,7 @@ function AnnotationHeader({ ...@@ -56,7 +56,7 @@ function AnnotationHeader({
threadIsCollapsed, threadIsCollapsed,
settings, settings,
}) { }) {
const store = useStoreProxy(); const store = useSidebarStore();
const defaultAuthority = store.defaultAuthority(); const defaultAuthority = store.defaultAuthority();
const displayNamesEnabled = store.isFeatureEnabled('client_display_names'); const displayNamesEnabled = store.isFeatureEnabled('client_display_names');
......
...@@ -64,7 +64,7 @@ describe('Annotation', () => { ...@@ -64,7 +64,7 @@ describe('Annotation', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../../helpers/annotation-metadata': fakeMetadata, '../../helpers/annotation-metadata': fakeMetadata,
'../../store/use-store': { useStoreProxy: () => fakeStore }, '../../store': { useSidebarStore: () => fakeStore },
}); });
}); });
......
...@@ -95,7 +95,7 @@ describe('AnnotationActionBar', () => { ...@@ -95,7 +95,7 @@ describe('AnnotationActionBar', () => {
annotationSharingLink: fakeAnnotationSharingLink, annotationSharingLink: fakeAnnotationSharingLink,
}, },
'../../helpers/permissions': { permits: fakePermits }, '../../helpers/permissions': { permits: fakePermits },
'../../store/use-store': { useStoreProxy: () => fakeStore }, '../../store': { useSidebarStore: () => fakeStore },
'../../../shared/prompts': { confirm: fakeConfirm }, '../../../shared/prompts': { confirm: fakeConfirm },
}); });
}); });
......
...@@ -60,7 +60,7 @@ describe('AnnotationBody', () => { ...@@ -60,7 +60,7 @@ describe('AnnotationBody', () => {
$imports.$mock({ $imports.$mock({
'../../helpers/account-id': { isThirdPartyUser: fakeIsThirdPartyUser }, '../../helpers/account-id': { isThirdPartyUser: fakeIsThirdPartyUser },
'../../helpers/theme': { applyTheme: fakeApplyTheme }, '../../helpers/theme': { applyTheme: fakeApplyTheme },
'../../store/use-store': { useStoreProxy: () => fakeStore }, '../../store': { useSidebarStore: () => fakeStore },
}); });
}); });
......
...@@ -64,7 +64,7 @@ describe('AnnotationEditor', () => { ...@@ -64,7 +64,7 @@ describe('AnnotationEditor', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../../store/use-store': { useStoreProxy: () => fakeStore }, '../../store': { useSidebarStore: () => fakeStore },
'../../helpers/theme': { applyTheme: fakeApplyTheme }, '../../helpers/theme': { applyTheme: fakeApplyTheme },
}); });
// `AnnotationLicense` is a presentation-only component and is only used // `AnnotationLicense` is a presentation-only component and is only used
......
...@@ -66,7 +66,7 @@ describe('AnnotationHeader', () => { ...@@ -66,7 +66,7 @@ describe('AnnotationHeader', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../../store/use-store': { useStoreProxy: () => fakeStore }, '../../store': { useSidebarStore: () => fakeStore },
'../../helpers/account-id': fakeAccountId, '../../helpers/account-id': fakeAccountId,
'../../helpers/annotation-metadata': { '../../helpers/annotation-metadata': {
domainAndTitle: fakeDomainAndTitle, domainAndTitle: fakeDomainAndTitle,
......
import { useEffect, useState } from 'preact/hooks'; import { useEffect, useState } from 'preact/hooks';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
import { useRootThread } from './hooks/use-root-thread'; import { useRootThread } from './hooks/use-root-thread';
...@@ -19,7 +19,7 @@ import SidebarContentError from './SidebarContentError'; ...@@ -19,7 +19,7 @@ import SidebarContentError from './SidebarContentError';
* @param {AnnotationViewProps} props * @param {AnnotationViewProps} props
*/ */
function AnnotationView({ loadAnnotationsService, onLogin }) { function AnnotationView({ loadAnnotationsService, onLogin }) {
const store = useStoreProxy(); const store = useSidebarStore();
const annotationId = store.routeParams().id; const annotationId = store.routeParams().id;
const rootThread = useRootThread(); const rootThread = useRootThread();
const userid = store.profile().userid; const userid = store.profile().userid;
......
...@@ -4,7 +4,7 @@ import classNames from 'classnames'; ...@@ -4,7 +4,7 @@ import classNames from 'classnames';
import { useMemo } from 'preact/hooks'; import { useMemo } from 'preact/hooks';
import { countVisible } from '../helpers/thread'; import { countVisible } from '../helpers/thread';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import { useRootThread } from './hooks/use-root-thread'; import { useRootThread } from './hooks/use-root-thread';
...@@ -69,7 +69,7 @@ function FilterStatusPanel({ ...@@ -69,7 +69,7 @@ function FilterStatusPanel({
focusDisplayName, focusDisplayName,
resultCount, resultCount,
}) { }) {
const store = useStoreProxy(); const store = useSidebarStore();
return ( return (
<Card classes="mb-3 p-3"> <Card classes="mb-3 p-3">
<div className="flex items-center justify-center space-x-1"> <div className="flex items-center justify-center space-x-1">
...@@ -133,7 +133,7 @@ function FilterStatusPanel({ ...@@ -133,7 +133,7 @@ function FilterStatusPanel({
* @param {FilterModeProps} props * @param {FilterModeProps} props
*/ */
function SelectionFilterStatus({ filterState, rootThread }) { function SelectionFilterStatus({ filterState, rootThread }) {
const store = useStoreProxy(); const store = useSidebarStore();
const directLinkedId = store.directLinkedAnnotationId(); const directLinkedId = store.directLinkedAnnotationId();
// The total number of top-level annotations (visible or not) // The total number of top-level annotations (visible or not)
const totalCount = store.annotationCount(); const totalCount = store.annotationCount();
...@@ -188,7 +188,7 @@ function SelectionFilterStatus({ filterState, rootThread }) { ...@@ -188,7 +188,7 @@ function SelectionFilterStatus({ filterState, rootThread }) {
* @param {FilterModeProps} props * @param {FilterModeProps} props
*/ */
function QueryFilterStatus({ filterState, rootThread }) { function QueryFilterStatus({ filterState, rootThread }) {
const store = useStoreProxy(); const store = useSidebarStore();
const visibleCount = countVisible(rootThread); const visibleCount = countVisible(rootThread);
const resultCount = visibleCount - filterState.forcedVisibleCount; const resultCount = visibleCount - filterState.forcedVisibleCount;
...@@ -233,7 +233,7 @@ function QueryFilterStatus({ filterState, rootThread }) { ...@@ -233,7 +233,7 @@ function QueryFilterStatus({ filterState, rootThread }) {
* @param {FilterModeProps} props * @param {FilterModeProps} props
*/ */
function FocusFilterStatus({ filterState, rootThread }) { function FocusFilterStatus({ filterState, rootThread }) {
const store = useStoreProxy(); const store = useSidebarStore();
const visibleCount = countVisible(rootThread); const visibleCount = countVisible(rootThread);
const resultCount = visibleCount - filterState.forcedVisibleCount; const resultCount = visibleCount - filterState.forcedVisibleCount;
...@@ -280,7 +280,7 @@ function FocusFilterStatus({ filterState, rootThread }) { ...@@ -280,7 +280,7 @@ function FocusFilterStatus({ filterState, rootThread }) {
export default function FilterStatus() { export default function FilterStatus() {
const rootThread = useRootThread(); const rootThread = useRootThread();
const store = useStoreProxy(); const store = useSidebarStore();
const focusState = store.focusState(); const focusState = store.focusState();
const forcedVisibleCount = store.forcedVisibleThreads().length; const forcedVisibleCount = store.forcedVisibleThreads().length;
const filterQuery = store.filterQuery(); const filterQuery = store.filterQuery();
......
...@@ -6,7 +6,7 @@ import { orgName } from '../../helpers/group-list-item-common'; ...@@ -6,7 +6,7 @@ import { orgName } from '../../helpers/group-list-item-common';
import { groupsByOrganization } from '../../helpers/group-organizations'; import { groupsByOrganization } from '../../helpers/group-organizations';
import { isThirdPartyService } from '../../helpers/is-third-party-service'; import { isThirdPartyService } from '../../helpers/is-third-party-service';
import { withServices } from '../../service-context'; import { withServices } from '../../service-context';
import { useStoreProxy } from '../../store/use-store'; import { useSidebarStore } from '../../store';
import Menu from '../Menu'; import Menu from '../Menu';
import MenuItem from '../MenuItem'; import MenuItem from '../MenuItem';
...@@ -41,7 +41,7 @@ function publisherProvidedIcon(settings) { ...@@ -41,7 +41,7 @@ function publisherProvidedIcon(settings) {
* @param {GroupListProps} props * @param {GroupListProps} props
*/ */
function GroupList({ settings }) { function GroupList({ settings }) {
const store = useStoreProxy(); const store = useSidebarStore();
const currentGroups = store.getCurrentlyViewingGroups(); const currentGroups = store.getCurrentlyViewingGroups();
const featuredGroups = store.getFeaturedGroups(); const featuredGroups = store.getFeaturedGroups();
const myGroups = store.getMyGroups(); const myGroups = store.getMyGroups();
......
import { orgName } from '../../helpers/group-list-item-common'; import { orgName } from '../../helpers/group-list-item-common';
import { withServices } from '../../service-context'; import { withServices } from '../../service-context';
import { useStoreProxy } from '../../store/use-store'; import { useSidebarStore } from '../../store';
import { copyText } from '../../util/copy-to-clipboard'; import { copyText } from '../../util/copy-to-clipboard';
import { confirm } from '../../../shared/prompts'; import { confirm } from '../../../shared/prompts';
...@@ -40,7 +40,7 @@ function GroupListItem({ ...@@ -40,7 +40,7 @@ function GroupListItem({
const isSelectable = const isSelectable =
(group.scopes && !group.scopes.enforced) || group.isScopedToUri; (group.scopes && !group.scopes.enforced) || group.isScopedToUri;
const store = useStoreProxy(); const store = useSidebarStore();
const focusedGroupId = store.focusedGroupId(); const focusedGroupId = store.focusedGroupId();
const isSelected = group.id === focusedGroupId; const isSelected = group.id === focusedGroupId;
......
...@@ -57,7 +57,7 @@ describe('GroupList', () => { ...@@ -57,7 +57,7 @@ describe('GroupList', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../../store/use-store': { useStoreProxy: () => fakeStore }, '../../store': { useSidebarStore: () => fakeStore },
'../../config/service-config': { serviceConfig: fakeServiceConfig }, '../../config/service-config': { serviceConfig: fakeServiceConfig },
}); });
}); });
......
...@@ -67,7 +67,7 @@ describe('GroupListItem', () => { ...@@ -67,7 +67,7 @@ describe('GroupListItem', () => {
copyText: fakeCopyText, copyText: fakeCopyText,
}, },
'../../helpers/group-list-item-common': fakeGroupListItemCommon, '../../helpers/group-list-item-common': fakeGroupListItemCommon,
'../../store/use-store': { useStoreProxy: () => fakeStore }, '../../store': { useSidebarStore: () => fakeStore },
'../../../shared/prompts': { confirm: fakeConfirm }, '../../../shared/prompts': { confirm: fakeConfirm },
}); });
}); });
......
import { Icon, Link, LinkButton } from '@hypothesis/frontend-shared'; import { Icon, Link, LinkButton } from '@hypothesis/frontend-shared';
import { useCallback, useMemo, useState } from 'preact/hooks'; import { useCallback, useMemo, useState } from 'preact/hooks';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
import { VersionData } from '../helpers/version-data'; import { VersionData } from '../helpers/version-data';
...@@ -63,7 +63,7 @@ function HelpPanelTab({ linkText, url }) { ...@@ -63,7 +63,7 @@ function HelpPanelTab({ linkText, url }) {
* @param {HelpPanelProps} props * @param {HelpPanelProps} props
*/ */
function HelpPanel({ auth, session }) { function HelpPanel({ auth, session }) {
const store = useStoreProxy(); const store = useSidebarStore();
const frames = store.frames(); const frames = store.frames();
const mainFrame = store.mainFrame(); const mainFrame = store.mainFrame();
......
...@@ -7,7 +7,7 @@ import { parseAccountID } from '../helpers/account-id'; ...@@ -7,7 +7,7 @@ import { parseAccountID } from '../helpers/account-id';
import { shouldAutoDisplayTutorial } from '../helpers/session'; import { shouldAutoDisplayTutorial } from '../helpers/session';
import { applyTheme } from '../helpers/theme'; import { applyTheme } from '../helpers/theme';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import AnnotationView from './AnnotationView'; import AnnotationView from './AnnotationView';
import SidebarView from './SidebarView'; import SidebarView from './SidebarView';
...@@ -68,7 +68,7 @@ function authStateFromProfile(profile) { ...@@ -68,7 +68,7 @@ function authStateFromProfile(profile) {
* @param {HypothesisAppProps} props * @param {HypothesisAppProps} props
*/ */
function HypothesisApp({ auth, frameSync, settings, session, toastMessenger }) { function HypothesisApp({ auth, frameSync, settings, session, toastMessenger }) {
const store = useStoreProxy(); const store = useSidebarStore();
const hasFetchedProfile = store.hasFetchedProfile(); const hasFetchedProfile = store.hasFetchedProfile();
const profile = store.profile(); const profile = store.profile();
const route = store.route(); const route = store.route();
......
import { Link, LinkButton, Icon } from '@hypothesis/frontend-shared'; import { Link, LinkButton, Icon } from '@hypothesis/frontend-shared';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
/** /**
* @typedef LoggedOutMessageProps * @typedef LoggedOutMessageProps
...@@ -15,7 +15,7 @@ import { useStoreProxy } from '../store/use-store'; ...@@ -15,7 +15,7 @@ import { useStoreProxy } from '../store/use-store';
* @param {LoggedOutMessageProps} props * @param {LoggedOutMessageProps} props
*/ */
function LoggedOutMessage({ onLogin }) { function LoggedOutMessage({ onLogin }) {
const store = useStoreProxy(); const store = useSidebarStore();
return ( return (
<div className="flex flex-col items-center m-6 space-y-6"> <div className="flex flex-col items-center m-6 space-y-6">
......
import { Actions, LabeledButton } from '@hypothesis/frontend-shared'; import { Actions, LabeledButton } from '@hypothesis/frontend-shared';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import SidebarPanel from './SidebarPanel'; import SidebarPanel from './SidebarPanel';
...@@ -16,7 +16,7 @@ import SidebarPanel from './SidebarPanel'; ...@@ -16,7 +16,7 @@ import SidebarPanel from './SidebarPanel';
* @param {LoginPromptPanelProps} props * @param {LoginPromptPanelProps} props
*/ */
export default function LoginPromptPanel({ onLogin, onSignUp }) { export default function LoginPromptPanel({ onLogin, onSignUp }) {
const store = useStoreProxy(); const store = useSidebarStore();
const isLoggedIn = store.isLoggedIn(); const isLoggedIn = store.isLoggedIn();
if (isLoggedIn) { if (isLoggedIn) {
return null; return null;
......
import { Icon, LabeledButton } from '@hypothesis/frontend-shared'; import { Icon, LabeledButton } from '@hypothesis/frontend-shared';
import classnames from 'classnames'; import classnames from 'classnames';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import * as annotationMetadata from '../helpers/annotation-metadata'; import * as annotationMetadata from '../helpers/annotation-metadata';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
...@@ -25,7 +25,7 @@ import { withServices } from '../service-context'; ...@@ -25,7 +25,7 @@ import { withServices } from '../service-context';
* @param {ModerationBannerProps} props * @param {ModerationBannerProps} props
*/ */
function ModerationBanner({ annotation, api, toastMessenger }) { function ModerationBanner({ annotation, api, toastMessenger }) {
const store = useStoreProxy(); const store = useSidebarStore();
const flagCount = annotationMetadata.flagCount(annotation); const flagCount = annotationMetadata.flagCount(annotation);
const isHiddenOrFlagged = const isHiddenOrFlagged =
......
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import { useUserFilterOptions } from './hooks/use-filter-options'; import { useUserFilterOptions } from './hooks/use-filter-options';
import FilterSelect from './FilterSelect'; import FilterSelect from './FilterSelect';
...@@ -11,7 +11,7 @@ import FilterSelect from './FilterSelect'; ...@@ -11,7 +11,7 @@ import FilterSelect from './FilterSelect';
* Filters for the Notebook * Filters for the Notebook
*/ */
function NotebookFilters() { function NotebookFilters() {
const store = useStoreProxy(); const store = useSidebarStore();
const userFilter = store.getFilter('user'); const userFilter = store.getFilter('user');
const userFilterOptions = useUserFilterOptions(); const userFilterOptions = useUserFilterOptions();
......
...@@ -4,7 +4,7 @@ import scrollIntoView from 'scroll-into-view'; ...@@ -4,7 +4,7 @@ import scrollIntoView from 'scroll-into-view';
import { ResultSizeError } from '../search-client'; import { ResultSizeError } from '../search-client';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import NotebookFilters from './NotebookFilters'; import NotebookFilters from './NotebookFilters';
import NotebookResultCount from './NotebookResultCount'; import NotebookResultCount from './NotebookResultCount';
...@@ -23,7 +23,7 @@ import { useRootThread } from './hooks/use-root-thread'; ...@@ -23,7 +23,7 @@ import { useRootThread } from './hooks/use-root-thread';
* @param {NotebookViewProps} props * @param {NotebookViewProps} props
*/ */
function NotebookView({ loadAnnotationsService, streamer }) { function NotebookView({ loadAnnotationsService, streamer }) {
const store = useStoreProxy(); const store = useSidebarStore();
const filters = store.getFilterValues(); const filters = store.getFilterValues();
const focusedGroup = store.focusedGroup(); const focusedGroup = store.focusedGroup();
......
...@@ -2,7 +2,7 @@ import { IconButton, Spinner } from '@hypothesis/frontend-shared'; ...@@ -2,7 +2,7 @@ import { IconButton, Spinner } from '@hypothesis/frontend-shared';
import classnames from 'classnames'; import classnames from 'classnames';
import { useRef, useState } from 'preact/hooks'; import { useRef, useState } from 'preact/hooks';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
/** /**
* @typedef SearchInputProps * @typedef SearchInputProps
...@@ -26,7 +26,7 @@ import { useStoreProxy } from '../store/use-store'; ...@@ -26,7 +26,7 @@ import { useStoreProxy } from '../store/use-store';
* @param {SearchInputProps} props * @param {SearchInputProps} props
*/ */
export default function SearchInput({ alwaysExpanded, query, onSearch }) { export default function SearchInput({ alwaysExpanded, query, onSearch }) {
const store = useStoreProxy(); const store = useSidebarStore();
const isLoading = store.isLoading(); const isLoading = store.isLoading();
const input = /** @type {{ current: HTMLInputElement }} */ (useRef()); const input = /** @type {{ current: HTMLInputElement }} */ (useRef());
......
...@@ -7,8 +7,8 @@ import { ...@@ -7,8 +7,8 @@ import {
import classnames from 'classnames'; import classnames from 'classnames';
import { applyTheme } from '../helpers/theme'; import { applyTheme } from '../helpers/theme';
import { useStoreProxy } from '../store/use-store';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
import { useSidebarStore } from '../store';
/** /**
* @typedef {import('../../types/config').SidebarSettings} SidebarSettings * @typedef {import('../../types/config').SidebarSettings} SidebarSettings
...@@ -91,7 +91,7 @@ function Tab({ ...@@ -91,7 +91,7 @@ function Tab({
* @param {SelectionTabsProps} props * @param {SelectionTabsProps} props
*/ */
function SelectionTabs({ annotationsService, isLoading, settings }) { function SelectionTabs({ annotationsService, isLoading, settings }) {
const store = useStoreProxy(); const store = useSidebarStore();
const selectedTab = store.selectedTab(); const selectedTab = store.selectedTab();
const noteCount = store.noteCount(); const noteCount = store.noteCount();
const annotationCount = store.annotationCount(); const annotationCount = store.annotationCount();
......
...@@ -6,7 +6,7 @@ import { ...@@ -6,7 +6,7 @@ import {
TextInputWithButton, TextInputWithButton,
} from '@hypothesis/frontend-shared'; } from '@hypothesis/frontend-shared';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import { pageSharingLink } from '../helpers/annotation-sharing'; import { pageSharingLink } from '../helpers/annotation-sharing';
import { copyText } from '../util/copy-to-clipboard'; import { copyText } from '../util/copy-to-clipboard';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
...@@ -30,7 +30,7 @@ import SidebarPanel from './SidebarPanel'; ...@@ -30,7 +30,7 @@ import SidebarPanel from './SidebarPanel';
* @param {ShareAnnotationsPanelProps} props * @param {ShareAnnotationsPanelProps} props
*/ */
function ShareAnnotationsPanel({ toastMessenger }) { function ShareAnnotationsPanel({ toastMessenger }) {
const store = useStoreProxy(); const store = useSidebarStore();
const mainFrame = store.mainFrame(); const mainFrame = store.mainFrame();
const focusedGroup = store.focusedGroup(); const focusedGroup = store.focusedGroup();
const groupName = (focusedGroup && focusedGroup.name) || '...'; const groupName = (focusedGroup && focusedGroup.name) || '...';
......
import { LabeledButton, Panel } from '@hypothesis/frontend-shared'; import { LabeledButton, Panel } from '@hypothesis/frontend-shared';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
/** /**
* @typedef SidebarContentErrorProps * @typedef SidebarContentErrorProps
...@@ -20,7 +20,7 @@ export default function SidebarContentError({ ...@@ -20,7 +20,7 @@ export default function SidebarContentError({
onLoginRequest, onLoginRequest,
showClearSelection = false, showClearSelection = false,
}) { }) {
const store = useStoreProxy(); const store = useSidebarStore();
const isLoggedIn = store.isLoggedIn(); const isLoggedIn = store.isLoggedIn();
const errorTitle = const errorTitle =
......
...@@ -2,7 +2,7 @@ import { Panel } from '@hypothesis/frontend-shared'; ...@@ -2,7 +2,7 @@ import { Panel } from '@hypothesis/frontend-shared';
import { useCallback, useEffect, useRef } from 'preact/hooks'; import { useCallback, useEffect, useRef } from 'preact/hooks';
import scrollIntoView from 'scroll-into-view'; import scrollIntoView from 'scroll-into-view';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import Slider from './Slider'; import Slider from './Slider';
...@@ -35,7 +35,7 @@ export default function SidebarPanel({ ...@@ -35,7 +35,7 @@ export default function SidebarPanel({
title, title,
onActiveChanged, onActiveChanged,
}) { }) {
const store = useStoreProxy(); const store = useSidebarStore();
const panelIsActive = store.isSidebarPanelOpen(panelName); const panelIsActive = store.isSidebarPanelOpen(panelName);
const panelElement = useRef(/** @type {HTMLDivElement|null}*/ (null)); const panelElement = useRef(/** @type {HTMLDivElement|null}*/ (null));
......
...@@ -2,7 +2,7 @@ import { useEffect, useRef } from 'preact/hooks'; ...@@ -2,7 +2,7 @@ import { useEffect, useRef } from 'preact/hooks';
import { useRootThread } from './hooks/use-root-thread'; import { useRootThread } from './hooks/use-root-thread';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import { tabForAnnotation } from '../helpers/tabs'; import { tabForAnnotation } from '../helpers/tabs';
import FilterStatus from './FilterStatus'; import FilterStatus from './FilterStatus';
...@@ -36,7 +36,7 @@ function SidebarView({ ...@@ -36,7 +36,7 @@ function SidebarView({
const rootThread = useRootThread(); const rootThread = useRootThread();
// Store state values // Store state values
const store = useStoreProxy(); const store = useSidebarStore();
const focusedGroupId = store.focusedGroupId(); const focusedGroupId = store.focusedGroupId();
const hasAppliedFilter = const hasAppliedFilter =
store.hasAppliedFilter() || store.hasSelectedAnnotations(); store.hasAppliedFilter() || store.hasSelectedAnnotations();
......
import { Icon } from '@hypothesis/frontend-shared'; import { Icon } from '@hypothesis/frontend-shared';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import Menu from './Menu'; import Menu from './Menu';
import MenuItem from './MenuItem'; import MenuItem from './MenuItem';
...@@ -9,7 +9,7 @@ import MenuItem from './MenuItem'; ...@@ -9,7 +9,7 @@ import MenuItem from './MenuItem';
* A drop-down menu of sorting options for a collection of annotations. * A drop-down menu of sorting options for a collection of annotations.
*/ */
export default function SortMenu() { export default function SortMenu() {
const store = useStoreProxy(); const store = useSidebarStore();
// The currently-applied sort order // The currently-applied sort order
const sortKey = store.sortKey(); const sortKey = store.sortKey();
// All available sorting options. These change depending on current // All available sorting options. These change depending on current
......
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
import SearchInput from './SearchInput'; import SearchInput from './SearchInput';
...@@ -16,7 +16,7 @@ import SearchInput from './SearchInput'; ...@@ -16,7 +16,7 @@ import SearchInput from './SearchInput';
* @param {StreamSearchInputProps} props * @param {StreamSearchInputProps} props
*/ */
function StreamSearchInput({ router }) { function StreamSearchInput({ router }) {
const store = useStoreProxy(); const store = useSidebarStore();
const query = store.routeParams().q; const query = store.routeParams().q;
/** @param {string} query */ /** @param {string} query */
const setQuery = query => { const setQuery = query => {
......
...@@ -3,7 +3,7 @@ import { useCallback, useEffect } from 'preact/hooks'; ...@@ -3,7 +3,7 @@ import { useCallback, useEffect } from 'preact/hooks';
import * as searchFilter from '../util/search-filter'; import * as searchFilter from '../util/search-filter';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
import { useRootThread } from './hooks/use-root-thread'; import { useRootThread } from './hooks/use-root-thread';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import ThreadList from './ThreadList'; import ThreadList from './ThreadList';
...@@ -19,7 +19,7 @@ import ThreadList from './ThreadList'; ...@@ -19,7 +19,7 @@ import ThreadList from './ThreadList';
* @param {StreamViewProps} props * @param {StreamViewProps} props
*/ */
function StreamView({ api, toastMessenger }) { function StreamView({ api, toastMessenger }) {
const store = useStoreProxy(); const store = useSidebarStore();
const currentQuery = store.routeParams().q; const currentQuery = store.routeParams().q;
/** /**
......
...@@ -2,7 +2,7 @@ import { IconButton, LabeledButton } from '@hypothesis/frontend-shared'; ...@@ -2,7 +2,7 @@ import { IconButton, LabeledButton } from '@hypothesis/frontend-shared';
import classnames from 'classnames'; import classnames from 'classnames';
import { useCallback, useMemo } from 'preact/hooks'; import { useCallback, useMemo } from 'preact/hooks';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
import { countHidden, countVisible } from '../helpers/thread'; import { countHidden, countVisible } from '../helpers/thread';
...@@ -26,7 +26,7 @@ import ModerationBanner from './ModerationBanner'; ...@@ -26,7 +26,7 @@ import ModerationBanner from './ModerationBanner';
* @param {boolean} props.threadIsCollapsed * @param {boolean} props.threadIsCollapsed
*/ */
function HiddenThreadCardHeader({ annotation, ...restProps }) { function HiddenThreadCardHeader({ annotation, ...restProps }) {
const store = useStoreProxy(); const store = useSidebarStore();
// These two lines are copied from the AnnotationHeader component to mimic the // These two lines are copied from the AnnotationHeader component to mimic the
// exact same behaviour. // exact same behaviour.
...@@ -129,7 +129,7 @@ function Thread({ thread, threadsService }) { ...@@ -129,7 +129,7 @@ function Thread({ thread, threadsService }) {
child => countVisible(child) > 0 child => countVisible(child) > 0
); );
const store = useStoreProxy(); const store = useSidebarStore();
const hasAppliedFilter = store.hasAppliedFilter(); const hasAppliedFilter = store.hasAppliedFilter();
const onToggleReplies = useCallback( const onToggleReplies = useCallback(
() => store.setExpanded(thread.id, !!thread.collapsed), () => store.setExpanded(thread.id, !!thread.collapsed),
......
...@@ -3,7 +3,7 @@ import classnames from 'classnames'; ...@@ -3,7 +3,7 @@ import classnames from 'classnames';
import debounce from 'lodash.debounce'; import debounce from 'lodash.debounce';
import { useCallback, useMemo } from 'preact/hooks'; import { useCallback, useMemo } from 'preact/hooks';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
import Thread from './Thread'; import Thread from './Thread';
...@@ -25,7 +25,7 @@ import Thread from './Thread'; ...@@ -25,7 +25,7 @@ import Thread from './Thread';
* @param {ThreadCardProps} props * @param {ThreadCardProps} props
*/ */
function ThreadCard({ frameSync, thread }) { function ThreadCard({ frameSync, thread }) {
const store = useStoreProxy(); const store = useSidebarStore();
const threadTag = thread.annotation?.$tag ?? null; const threadTag = thread.annotation?.$tag ?? null;
const isFocused = threadTag && store.isAnnotationFocused(threadTag); const isFocused = threadTag && store.isAnnotationFocused(threadTag);
const focusThreadAnnotation = useMemo( const focusThreadAnnotation = useMemo(
......
...@@ -7,7 +7,7 @@ import { ...@@ -7,7 +7,7 @@ import {
calculateVisibleThreads, calculateVisibleThreads,
THREAD_DIMENSION_DEFAULTS, THREAD_DIMENSION_DEFAULTS,
} from '../helpers/visible-threads'; } from '../helpers/visible-threads';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import { getElementHeightWithMargins } from '../util/dom'; import { getElementHeightWithMargins } from '../util/dom';
import ThreadCard from './ThreadCard'; import ThreadCard from './ThreadCard';
...@@ -109,7 +109,7 @@ function ThreadList({ threads }) { ...@@ -109,7 +109,7 @@ function ThreadList({ threads }) {
[topLevelThreads, threadHeights, scrollPosition, scrollContainerHeight] [topLevelThreads, threadHeights, scrollPosition, scrollContainerHeight]
); );
const store = useStoreProxy(); const store = useSidebarStore();
// Get the `$tag` of the most recently created unsaved annotation. // Get the `$tag` of the most recently created unsaved annotation.
const newAnnotationTag = (() => { const newAnnotationTag = (() => {
......
import classnames from 'classnames'; import classnames from 'classnames';
import { Icon } from '@hypothesis/frontend-shared'; import { Icon } from '@hypothesis/frontend-shared';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
/** /**
...@@ -86,7 +86,7 @@ function ToastMessage({ message, onDismiss }) { ...@@ -86,7 +86,7 @@ function ToastMessage({ message, onDismiss }) {
* @param {ToastMessagesProps} props * @param {ToastMessagesProps} props
*/ */
function ToastMessages({ toastMessenger }) { function ToastMessages({ toastMessenger }) {
const store = useStoreProxy(); const store = useSidebarStore();
const messages = store.getToastMessages(); const messages = store.getToastMessages();
return ( return (
<div> <div>
......
...@@ -5,7 +5,7 @@ import { serviceConfig } from '../config/service-config'; ...@@ -5,7 +5,7 @@ import { serviceConfig } from '../config/service-config';
import { isThirdPartyService } from '../helpers/is-third-party-service'; import { isThirdPartyService } from '../helpers/is-third-party-service';
import { applyTheme } from '../helpers/theme'; import { applyTheme } from '../helpers/theme';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import GroupList from './GroupList'; import GroupList from './GroupList';
import SearchInput from './SearchInput'; import SearchInput from './SearchInput';
...@@ -53,7 +53,7 @@ function TopBar({ ...@@ -53,7 +53,7 @@ function TopBar({
const showSharePageButton = !isThirdPartyService(settings); const showSharePageButton = !isThirdPartyService(settings);
const loginLinkStyle = applyTheme(['accentColor'], settings); const loginLinkStyle = applyTheme(['accentColor'], settings);
const store = useStoreProxy(); const store = useSidebarStore();
const filterQuery = store.filterQuery(); const filterQuery = store.filterQuery();
const pendingUpdateCount = store.pendingUpdateCount(); const pendingUpdateCount = store.pendingUpdateCount();
......
...@@ -4,7 +4,7 @@ import { useState } from 'preact/hooks'; ...@@ -4,7 +4,7 @@ import { useState } from 'preact/hooks';
import { serviceConfig } from '../config/service-config'; import { serviceConfig } from '../config/service-config';
import { isThirdPartyUser } from '../helpers/account-id'; import { isThirdPartyUser } from '../helpers/account-id';
import { withServices } from '../service-context'; import { withServices } from '../service-context';
import { useStoreProxy } from '../store/use-store'; import { useSidebarStore } from '../store';
import Menu from './Menu'; import Menu from './Menu';
import MenuItem from './MenuItem'; import MenuItem from './MenuItem';
...@@ -40,7 +40,7 @@ import MenuSection from './MenuSection'; ...@@ -40,7 +40,7 @@ import MenuSection from './MenuSection';
* @param {UserMenuProps} props * @param {UserMenuProps} props
*/ */
function UserMenu({ auth, frameSync, onLogout, settings }) { function UserMenu({ auth, frameSync, onLogout, settings }) {
const store = useStoreProxy(); const store = useSidebarStore();
const defaultAuthority = store.defaultAuthority(); const defaultAuthority = store.defaultAuthority();
const isThirdParty = isThirdPartyUser(auth.userid, defaultAuthority); const isThirdParty = isThirdPartyUser(auth.userid, defaultAuthority);
......
...@@ -60,7 +60,7 @@ describe('sidebar/components/hooks/use-user-filter-options', () => { ...@@ -60,7 +60,7 @@ describe('sidebar/components/hooks/use-user-filter-options', () => {
$imports.$mock({ $imports.$mock({
'../../helpers/account-id': fakeAccountId, '../../helpers/account-id': fakeAccountId,
'../../helpers/annotation-user': fakeAnnotationUser, '../../helpers/annotation-user': fakeAnnotationUser,
'../../store/use-store': { useStoreProxy: () => fakeStore }, '../../store': { useSidebarStore: () => fakeStore },
}); });
}); });
......
...@@ -18,7 +18,7 @@ describe('sidebar/components/hooks/use-root-thread', () => { ...@@ -18,7 +18,7 @@ describe('sidebar/components/hooks/use-root-thread', () => {
fakeThreadAnnotations = sinon.stub().returns('fakeThreadAnnotations'); fakeThreadAnnotations = sinon.stub().returns('fakeThreadAnnotations');
$imports.$mock({ $imports.$mock({
'../../store/use-store': { useStoreProxy: () => fakeStore }, '../../store': { useSidebarStore: () => fakeStore },
'../../helpers/thread-annotations': { '../../helpers/thread-annotations': {
threadAnnotations: fakeThreadAnnotations, threadAnnotations: fakeThreadAnnotations,
}, },
......
import { useMemo } from 'preact/hooks'; import { useMemo } from 'preact/hooks';
import { useStoreProxy } from '../../store/use-store'; import { useSidebarStore } from '../../store';
import { isThirdPartyUser, username } from '../../helpers/account-id'; import { isThirdPartyUser, username } from '../../helpers/account-id';
import { annotationDisplayName } from '../../helpers/annotation-user'; import { annotationDisplayName } from '../../helpers/annotation-user';
...@@ -13,7 +13,7 @@ import { annotationDisplayName } from '../../helpers/annotation-user'; ...@@ -13,7 +13,7 @@ import { annotationDisplayName } from '../../helpers/annotation-user';
* @return {FilterOption[]} * @return {FilterOption[]}
*/ */
export function useUserFilterOptions() { export function useUserFilterOptions() {
const store = useStoreProxy(); const store = useSidebarStore();
const annotations = store.allAnnotations(); const annotations = store.allAnnotations();
const defaultAuthority = store.defaultAuthority(); const defaultAuthority = store.defaultAuthority();
const displayNamesEnabled = store.isFeatureEnabled('client_display_names'); const displayNamesEnabled = store.isFeatureEnabled('client_display_names');
......
import { useMemo } from 'preact/hooks'; import { useMemo } from 'preact/hooks';
import { useStoreProxy } from '../../store/use-store'; import { useSidebarStore } from '../../store';
import { threadAnnotations } from '../../helpers/thread-annotations'; import { threadAnnotations } from '../../helpers/thread-annotations';
/** @typedef {import('../../helpers/build-thread').Thread} Thread */ /** @typedef {import('../../helpers/build-thread').Thread} Thread */
...@@ -12,7 +12,7 @@ import { threadAnnotations } from '../../helpers/thread-annotations'; ...@@ -12,7 +12,7 @@ import { threadAnnotations } from '../../helpers/thread-annotations';
* @return {Thread} * @return {Thread}
*/ */
export function useRootThread() { export function useRootThread() {
const store = useStoreProxy(); const store = useSidebarStore();
const annotations = store.allAnnotations(); const annotations = store.allAnnotations();
const query = store.filterQuery(); const query = store.filterQuery();
const route = store.route(); const route = store.route();
......
...@@ -32,7 +32,7 @@ describe('AnnotationView', () => { ...@@ -32,7 +32,7 @@ describe('AnnotationView', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'./hooks/use-root-thread': { useRootThread: fakeUseRootThread }, './hooks/use-root-thread': { useRootThread: fakeUseRootThread },
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
}); });
}); });
......
...@@ -53,7 +53,7 @@ describe('FilterStatus', () => { ...@@ -53,7 +53,7 @@ describe('FilterStatus', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'./hooks/use-root-thread': { useRootThread: fakeUseRootThread }, './hooks/use-root-thread': { useRootThread: fakeUseRootThread },
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
'../helpers/thread': fakeThreadUtil, '../helpers/thread': fakeThreadUtil,
}); });
}); });
......
...@@ -39,7 +39,7 @@ describe('HelpPanel', () => { ...@@ -39,7 +39,7 @@ describe('HelpPanel', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
'../helpers/version-data': { VersionData: FakeVersionData }, '../helpers/version-data': { VersionData: FakeVersionData },
}); });
}); });
......
...@@ -79,7 +79,7 @@ describe('HypothesisApp', () => { ...@@ -79,7 +79,7 @@ describe('HypothesisApp', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../config/service-config': { serviceConfig: fakeServiceConfig }, '../config/service-config': { serviceConfig: fakeServiceConfig },
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
'../helpers/session': { '../helpers/session': {
shouldAutoDisplayTutorial: fakeShouldAutoDisplayTutorial, shouldAutoDisplayTutorial: fakeShouldAutoDisplayTutorial,
}, },
......
...@@ -19,7 +19,7 @@ describe('LoggedOutMessage', () => { ...@@ -19,7 +19,7 @@ describe('LoggedOutMessage', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
}); });
}); });
......
...@@ -31,7 +31,7 @@ describe('LoginPromptPanel', () => { ...@@ -31,7 +31,7 @@ describe('LoginPromptPanel', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
}); });
}); });
......
...@@ -41,7 +41,7 @@ describe('ModerationBanner', () => { ...@@ -41,7 +41,7 @@ describe('ModerationBanner', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
}); });
}); });
......
...@@ -26,7 +26,7 @@ describe('NotebookFilters', () => { ...@@ -26,7 +26,7 @@ describe('NotebookFilters', () => {
'./hooks/use-filter-options': { './hooks/use-filter-options': {
useUserFilterOptions: fakeUseUserFilterOptions, useUserFilterOptions: fakeUseUserFilterOptions,
}, },
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
}); });
}); });
......
...@@ -43,7 +43,7 @@ describe('NotebookView', () => { ...@@ -43,7 +43,7 @@ describe('NotebookView', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'./hooks/use-root-thread': { useRootThread: fakeUseRootThread }, './hooks/use-root-thread': { useRootThread: fakeUseRootThread },
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
'scroll-into-view': fakeScrollIntoView, 'scroll-into-view': fakeScrollIntoView,
}); });
}); });
......
...@@ -24,7 +24,7 @@ describe('SearchInput', () => { ...@@ -24,7 +24,7 @@ describe('SearchInput', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
}); });
}); });
......
...@@ -46,7 +46,7 @@ describe('SelectionTabs', () => { ...@@ -46,7 +46,7 @@ describe('SelectionTabs', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
}); });
}); });
......
...@@ -45,7 +45,7 @@ describe('ShareAnnotationsPanel', () => { ...@@ -45,7 +45,7 @@ describe('ShareAnnotationsPanel', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
'../helpers/annotation-sharing': { '../helpers/annotation-sharing': {
pageSharingLink: fakePageSharingLink, pageSharingLink: fakePageSharingLink,
}, },
......
...@@ -25,7 +25,7 @@ describe('SidebarContentError', () => { ...@@ -25,7 +25,7 @@ describe('SidebarContentError', () => {
isLoggedIn: sinon.stub().returns(true), isLoggedIn: sinon.stub().returns(true),
}; };
$imports.$mock({ $imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
}); });
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
}); });
......
...@@ -22,7 +22,7 @@ describe('SidebarPanel', () => { ...@@ -22,7 +22,7 @@ describe('SidebarPanel', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
'scroll-into-view': fakeScrollIntoView, 'scroll-into-view': fakeScrollIntoView,
}); });
}); });
......
...@@ -69,7 +69,7 @@ describe('SidebarView', () => { ...@@ -69,7 +69,7 @@ describe('SidebarView', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'./hooks/use-root-thread': { useRootThread: fakeUseRootThread }, './hooks/use-root-thread': { useRootThread: fakeUseRootThread },
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
'../helpers/tabs': fakeTabsUtil, '../helpers/tabs': fakeTabsUtil,
}); });
}); });
......
...@@ -21,7 +21,7 @@ describe('SortMenu', () => { ...@@ -21,7 +21,7 @@ describe('SortMenu', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
}); });
}); });
......
...@@ -18,7 +18,7 @@ describe('StreamSearchInput', () => { ...@@ -18,7 +18,7 @@ describe('StreamSearchInput', () => {
}; };
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
}); });
}); });
......
...@@ -41,7 +41,7 @@ describe('StreamView', () => { ...@@ -41,7 +41,7 @@ describe('StreamView', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'./hooks/use-root-thread': { useRootThread: fakeUseRootThread }, './hooks/use-root-thread': { useRootThread: fakeUseRootThread },
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
'../util/search-filter': fakeSearchFilter, '../util/search-filter': fakeSearchFilter,
}); });
}); });
......
...@@ -98,7 +98,7 @@ describe('Thread', () => { ...@@ -98,7 +98,7 @@ describe('Thread', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
'../helpers/thread': fakeThreadUtil, '../helpers/thread': fakeThreadUtil,
}); });
}); });
......
...@@ -38,7 +38,7 @@ describe('ThreadCard', () => { ...@@ -38,7 +38,7 @@ describe('ThreadCard', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'lodash.debounce': fakeDebounce, 'lodash.debounce': fakeDebounce,
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
}); });
}); });
......
...@@ -66,7 +66,7 @@ describe('ThreadList', () => { ...@@ -66,7 +66,7 @@ describe('ThreadList', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
'../util/dom': fakeDomUtil, '../util/dom': fakeDomUtil,
'../helpers/visible-threads': fakeVisibleThreadsUtil, '../helpers/visible-threads': fakeVisibleThreadsUtil,
}); });
......
...@@ -55,7 +55,7 @@ describe('ToastMessages', () => { ...@@ -55,7 +55,7 @@ describe('ToastMessages', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
}); });
}); });
......
...@@ -40,7 +40,7 @@ describe('TopBar', () => { ...@@ -40,7 +40,7 @@ describe('TopBar', () => {
'./SidebarContent': true, './SidebarContent': true,
}); });
$imports.$mock({ $imports.$mock({
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
'../helpers/is-third-party-service': { '../helpers/is-third-party-service': {
isThirdPartyService: fakeIsThirdPartyService, isThirdPartyService: fakeIsThirdPartyService,
}, },
......
...@@ -54,7 +54,7 @@ describe('UserMenu', () => { ...@@ -54,7 +54,7 @@ describe('UserMenu', () => {
isThirdPartyUser: fakeIsThirdPartyUser, isThirdPartyUser: fakeIsThirdPartyUser,
}, },
'../config/service-config': { serviceConfig: fakeServiceConfig }, '../config/service-config': { serviceConfig: fakeServiceConfig },
'../store/use-store': { useStoreProxy: () => fakeStore }, '../store': { useSidebarStore: () => fakeStore },
}); });
}); });
......
...@@ -187,9 +187,9 @@ function assignOnce(target, source) { ...@@ -187,9 +187,9 @@ function assignOnce(target, source) {
* selector and action methods rather than `getState` or `dispatch`. This * selector and action methods rather than `getState` or `dispatch`. This
* makes it easier to refactor the internal state structure. * makes it easier to refactor the internal state structure.
* *
* Preact UI components access stores via the `useStoreProxy` hook defined in * Preact UI components access stores via the `useStore` hook. This returns a
* `use-store.js`. This returns a proxy which enables UI components to observe * proxy which enables UI components to observe what store state a component
* what store state a component depends upon and re-render when it changes. * depends upon and re-render when it changes.
* *
* @template {readonly Module<any,any,any,any>[]} Modules * @template {readonly Module<any,any,any,any>[]} Modules
* @param {Modules} modules * @param {Modules} modules
......
import { useService } from '../service-context';
import { createStore } from './create-store'; import { createStore } from './create-store';
import { debugMiddleware } from './debug-middleware'; import { debugMiddleware } from './debug-middleware';
import { activityModule } from './modules/activity'; import { activityModule } from './modules/activity';
...@@ -16,6 +17,7 @@ import { sessionModule } from './modules/session'; ...@@ -16,6 +17,7 @@ import { sessionModule } from './modules/session';
import { sidebarPanelsModule } from './modules/sidebar-panels'; import { sidebarPanelsModule } from './modules/sidebar-panels';
import { toastMessagesModule } from './modules/toast-messages'; import { toastMessagesModule } from './modules/toast-messages';
import { viewerModule } from './modules/viewer'; import { viewerModule } from './modules/viewer';
import { useStore } from './use-store';
/** @typedef {ReturnType<createSidebarStore>} SidebarStore */ /** @typedef {ReturnType<createSidebarStore>} SidebarStore */
...@@ -56,3 +58,15 @@ export function createSidebarStore(settings) { ...@@ -56,3 +58,15 @@ export function createSidebarStore(settings) {
]); ]);
return createStore(modules, [settings], middleware); return createStore(modules, [settings], middleware);
} }
/**
* Hook for accessing the sidebar's store in UI components.
*
* Returns a wrapper around the store which tracks its usage by the component
* and re-renders the component when relevant data in the store changes. See
* {@link useStore}.
*/
export function useSidebarStore() {
const store = /** @type {SidebarStore} */ (useService('store'));
return useStore(store);
}
import { render } from 'preact';
import { act } from 'preact/test-utils';
import * as annotationFixtures from '../../test/annotation-fixtures'; import * as annotationFixtures from '../../test/annotation-fixtures';
import { createSidebarStore } from '../index'; import { createSidebarStore, useSidebarStore } from '../index';
import { immutable } from '../../util/immutable'; import { immutable } from '../../util/immutable';
import { ServiceContext } from '../../service-context';
const defaultAnnotation = annotationFixtures.defaultAnnotation; const defaultAnnotation = annotationFixtures.defaultAnnotation;
const newAnnotation = annotationFixtures.newAnnotation; const newAnnotation = annotationFixtures.newAnnotation;
...@@ -148,3 +152,45 @@ describe('createSidebarStore', () => { ...@@ -148,3 +152,45 @@ describe('createSidebarStore', () => {
}); });
}); });
}); });
describe('useSidebarStore', () => {
function AnnotationCard({ id }) {
const store = useSidebarStore();
const ann = store.findAnnotationByID(id);
return <div>{ann.text}</div>;
}
// `useSidebarStore` is a trivial wrapper, so rather than mock its dependencies,
// this is a more useful integration test that covers interaction of the store
// and UI components.
it('returns wrapper for components to interact with store', () => {
const store = createSidebarStore({});
const annot = { ...defaultAnnotation(), text: 'Initial text' };
store.addAnnotations([annot]);
const services = {
get(service) {
return service === 'store' ? store : null;
},
};
const el = document.createElement('div');
act(() => {
render(
<ServiceContext.Provider value={services}>
<AnnotationCard id={annot.id} />
</ServiceContext.Provider>,
el
);
});
assert.equal(el.innerHTML, '<div>Initial text</div>');
act(() => {
const updatedAnnot = { ...annot, text: 'Updated text' };
store.addAnnotations([updatedAnnot]);
});
assert.equal(el.innerHTML, '<div>Updated text</div>');
render(null, el); // Force unmount and cleanup of subscribers
});
});
...@@ -2,7 +2,7 @@ import { mount } from 'enzyme'; ...@@ -2,7 +2,7 @@ import { mount } from 'enzyme';
import { act } from 'preact/test-utils'; import { act } from 'preact/test-utils';
import { createStore, createStoreModule } from '../create-store'; import { createStore, createStoreModule } from '../create-store';
import { useStoreProxy, $imports } from '../use-store'; import { useStore } from '../use-store';
// Store module for use with `createStore` in tests. // Store module for use with `createStore` in tests.
const initialState = () => ({ things: [] }); const initialState = () => ({ things: [] });
...@@ -36,34 +36,27 @@ const thingsModule = createStoreModule(initialState, { ...@@ -36,34 +36,27 @@ const thingsModule = createStoreModule(initialState, {
}); });
describe('sidebar/store/use-store', () => { describe('sidebar/store/use-store', () => {
afterEach(() => { describe('useStore', () => {
$imports.$restore();
});
describe('useStoreProxy', () => {
let store; let store;
let renderCount; let renderCount;
beforeEach(() => { beforeEach(() => {
renderCount = 0; renderCount = 0;
store = createStore([thingsModule]); store = createStore([thingsModule]);
store.addThing('foo'); store.addThing('foo');
store.addThing('bar'); store.addThing('bar');
$imports.$mock({
'../service-context': {
useService: name => (name === 'store' ? store : null),
},
});
}); });
function useTestStore() {
return useStore(store);
}
function renderTestComponent() { function renderTestComponent() {
let proxy; let proxy;
const TestComponent = () => { const TestComponent = () => {
++renderCount; ++renderCount;
proxy = useStoreProxy(); proxy = useTestStore();
return <div>{proxy.thingCount()}</div>; return <div>{proxy.thingCount()}</div>;
}; };
......
import { useEffect, useRef, useReducer } from 'preact/hooks'; import { useEffect, useRef, useReducer } from 'preact/hooks';
import { useService } from '../service-context';
/** @typedef {import("redux").Store} Store */
/** @typedef {import("./index").SidebarStore} SidebarStore */
/** /**
* Result of a cached store selector method call. * Result of a cached store selector method call.
*/ */
...@@ -35,18 +29,26 @@ class CacheEntry { ...@@ -35,18 +29,26 @@ class CacheEntry {
} }
/** /**
* Return a wrapper around the `store` service that UI components can use to * Return a wrapper around a store that UI components can use to read from and
* extract data from the store and call actions on it. * modify data in it.
* *
* Unlike using the `store` service directly, the wrapper tracks what data from * Unlike using the store directly, the wrapper tracks what data from
* the store the current component uses, via selector methods, and re-renders the * the store the current component uses, by recording calls to selector methods,
* component when that data changes. * and re-renders the components when the results of those calls change.
* *
* The returned wrapper has the same API as the store itself. * The returned wrapper has the same API as the store itself.
* *
* @example * @example
* // A hook which encapsulates looking up the specific store instance,
* // eg. via `useContext`.
* function useAppStore() {
* // Get the store from somewhere, eg. a prop or context.
* const appStore = ...;
* return useStore(store);
* }
*
* function MyComponent() { * function MyComponent() {
* const store = useStoreProxy(); * const store = useAppStore();
* const currentUser = store.currentUser(); * const currentUser = store.currentUser();
* *
* return ( * return (
...@@ -57,11 +59,11 @@ class CacheEntry { ...@@ -57,11 +59,11 @@ class CacheEntry {
* ); * );
* } * }
* *
* @return {SidebarStore} * @template {import('./create-store').Store<unknown, unknown, unknown>} Store
* @param {Store} store - The store to wrap
* @return {Store} - A proxy with the same API as `store`
*/ */
export function useStoreProxy() { export function useStore(store) {
const store = /** @type {SidebarStore} */ (useService('store'));
// Hack to trigger a component re-render. // Hack to trigger a component re-render.
const [, forceUpdate] = useReducer(x => x + 1, 0); const [, forceUpdate] = useReducer(x => x + 1, 0);
...@@ -75,7 +77,7 @@ export function useStoreProxy() { ...@@ -75,7 +77,7 @@ export function useStoreProxy() {
const cache = cacheRef.current; const cache = cacheRef.current;
// Create the wrapper around the store. // Create the wrapper around the store.
const proxy = useRef(/** @type {SidebarStore|null} */ (null)); const proxy = useRef(/** @type {Store|null} */ (null));
if (!proxy.current) { if (!proxy.current) {
// Cached method wrappers. // Cached method wrappers.
/** @type {Map<string, Function>} */ /** @type {Map<string, Function>} */
...@@ -147,5 +149,5 @@ export function useStoreProxy() { ...@@ -147,5 +149,5 @@ export function useStoreProxy() {
return cleanup; return cleanup;
}, [cache, store]); }, [cache, store]);
return /** @type {SidebarStore} */ (proxy.current); return /** @type {Store} */ (proxy.current);
} }
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