Commit bcd4f9b5 authored by Lyza Danger Gardner's avatar Lyza Danger Gardner Committed by Lyza Gardner

Add `useRootThread` hook

Refactor root-thread usage by UI components to be via a preact hook
parent e76b5470
...@@ -4,6 +4,7 @@ import propTypes from 'prop-types'; ...@@ -4,6 +4,7 @@ import propTypes from 'prop-types';
import useStore from '../store/use-store'; import useStore from '../store/use-store';
import { withServices } from '../util/service-context'; import { withServices } from '../util/service-context';
import useRootThread from './hooks/use-root-thread';
import ThreadList from './thread-list'; import ThreadList from './thread-list';
import SidebarContentError from './sidebar-content-error'; import SidebarContentError from './sidebar-content-error';
...@@ -11,17 +12,11 @@ import SidebarContentError from './sidebar-content-error'; ...@@ -11,17 +12,11 @@ import SidebarContentError from './sidebar-content-error';
/** /**
* The main content for the single annotation page (aka. https://hypothes.is/a/<annotation ID>) * The main content for the single annotation page (aka. https://hypothes.is/a/<annotation ID>)
*/ */
function AnnotationViewerContent({ function AnnotationViewerContent({ loadAnnotationsService, onLogin }) {
loadAnnotationsService,
onLogin,
rootThread: rootThreadService,
}) {
const annotationId = useStore(store => store.routeParams().id); const annotationId = useStore(store => store.routeParams().id);
const clearAnnotations = useStore(store => store.clearAnnotations); const clearAnnotations = useStore(store => store.clearAnnotations);
const highlightAnnotations = useStore(store => store.highlightAnnotations); const highlightAnnotations = useStore(store => store.highlightAnnotations);
const rootThread = useStore(store => const rootThread = useRootThread();
rootThreadService.thread(store.getState())
);
const setExpanded = useStore(store => store.setExpanded); const setExpanded = useStore(store => store.setExpanded);
const userid = useStore(store => store.profile().userid); const userid = useStore(store => store.profile().userid);
...@@ -100,12 +95,8 @@ AnnotationViewerContent.propTypes = { ...@@ -100,12 +95,8 @@ AnnotationViewerContent.propTypes = {
// Injected. // Injected.
loadAnnotationsService: propTypes.object, loadAnnotationsService: propTypes.object,
rootThread: propTypes.object,
}; };
AnnotationViewerContent.injectedProps = [ AnnotationViewerContent.injectedProps = ['loadAnnotationsService'];
'loadAnnotationsService',
'rootThread',
];
export default withServices(AnnotationViewerContent); export default withServices(AnnotationViewerContent);
import { useService } from '../../util/service-context';
import useStore from '../../store/use-store';
/**
* Gather together state relevant to building a root thread of annotations and
* replies and return an updated root thread when changes occur.
*/
export default function useRootThread() {
const rootThreadService = useService('rootThread');
// Use a to-be-written selector to get relevant selection state, e.g.
// filters and forced-visible annotations, etc.
// const selectionState = useStore(store => store.getSelectionState());
// const route = useStore(store => store.routeName());
// const annotations = useStore(store => store.annotations());
// return useMemo(() => rootThreadService.buildRootThread(annotations, selectionState, route), [annotations, selectionState, route]);
return useStore(store => rootThreadService.thread(store.getState()));
}
import { createElement } from 'preact'; import { createElement } from 'preact';
import { useMemo } from 'preact/hooks'; import { useMemo } from 'preact/hooks';
import propTypes from 'prop-types';
import useStore from '../store/use-store'; import useStore from '../store/use-store';
import uiConstants from '../ui-constants'; import uiConstants from '../ui-constants';
import { withServices } from '../util/service-context'; import useRootThread from './hooks/use-root-thread';
import Button from './button'; import Button from './button';
...@@ -25,8 +24,8 @@ const countVisibleAnns = annThread => { ...@@ -25,8 +24,8 @@ const countVisibleAnns = annThread => {
* UI for displaying information about the currently-applied filtering of * UI for displaying information about the currently-applied filtering of
* annotations, and, in some cases, a mechanism for clearing the filter(s). * annotations, and, in some cases, a mechanism for clearing the filter(s).
* */ * */
function SearchStatusBar({ rootThread }) { function SearchStatusBar() {
const thread = useStore(store => rootThread.thread(store.getState())); const thread = useRootThread();
const actions = useStore(store => ({ const actions = useStore(store => ({
clearSelection: store.clearSelection, clearSelection: store.clearSelection,
...@@ -170,11 +169,7 @@ function SearchStatusBar({ rootThread }) { ...@@ -170,11 +169,7 @@ function SearchStatusBar({ rootThread }) {
); );
} }
SearchStatusBar.propTypes = { // Necessary for test-mocking purposes (`mockImportedComponents`)
// Injected services. SearchStatusBar.propTypes = {};
rootThread: propTypes.object.isRequired,
};
SearchStatusBar.injectedProps = ['rootThread'];
export default withServices(SearchStatusBar); export default SearchStatusBar;
...@@ -2,6 +2,7 @@ import { createElement } from 'preact'; ...@@ -2,6 +2,7 @@ import { createElement } from 'preact';
import propTypes from 'prop-types'; import propTypes from 'prop-types';
import { useEffect, useRef } from 'preact/hooks'; import { useEffect, useRef } from 'preact/hooks';
import useRootThread from './hooks/use-root-thread';
import { withServices } from '../util/service-context'; import { withServices } from '../util/service-context';
import useStore from '../store/use-store'; import useStore from '../store/use-store';
import { tabForAnnotation } from '../util/tabs'; import { tabForAnnotation } from '../util/tabs';
...@@ -22,12 +23,9 @@ function SidebarContent({ ...@@ -22,12 +23,9 @@ function SidebarContent({
onLogin, onLogin,
onSignUp, onSignUp,
loadAnnotationsService, loadAnnotationsService,
rootThread: rootThreadService,
streamer, streamer,
}) { }) {
const rootThread = useStore(store => const rootThread = useRootThread();
rootThreadService.thread(store.getState())
);
// Store state values // Store state values
const focusedGroupId = useStore(store => store.focusedGroupId()); const focusedGroupId = useStore(store => store.focusedGroupId());
...@@ -153,14 +151,12 @@ SidebarContent.propTypes = { ...@@ -153,14 +151,12 @@ SidebarContent.propTypes = {
// Injected // Injected
frameSync: propTypes.object, frameSync: propTypes.object,
loadAnnotationsService: propTypes.object, loadAnnotationsService: propTypes.object,
rootThread: propTypes.object,
streamer: propTypes.object, streamer: propTypes.object,
}; };
SidebarContent.injectedProps = [ SidebarContent.injectedProps = [
'frameSync', 'frameSync',
'loadAnnotationsService', 'loadAnnotationsService',
'rootThread',
'streamer', 'streamer',
]; ];
......
...@@ -4,6 +4,7 @@ import propTypes from 'prop-types'; ...@@ -4,6 +4,7 @@ import propTypes from 'prop-types';
import * as searchFilter from '../util/search-filter'; import * as searchFilter from '../util/search-filter';
import { withServices } from '../util/service-context'; import { withServices } from '../util/service-context';
import useRootThread from './hooks/use-root-thread';
import useStore from '../store/use-store'; import useStore from '../store/use-store';
import ThreadList from './thread-list'; import ThreadList from './thread-list';
...@@ -11,7 +12,7 @@ import ThreadList from './thread-list'; ...@@ -11,7 +12,7 @@ import ThreadList from './thread-list';
/** /**
* The main content of the "stream" route (https://hypothes.is/stream) * The main content of the "stream" route (https://hypothes.is/stream)
*/ */
function StreamContent({ api, rootThread: rootThreadService, toastMessenger }) { function StreamContent({ api, toastMessenger }) {
const addAnnotations = useStore(store => store.addAnnotations); const addAnnotations = useStore(store => store.addAnnotations);
const annotationFetchStarted = useStore( const annotationFetchStarted = useStore(
store => store.annotationFetchStarted store => store.annotationFetchStarted
...@@ -68,9 +69,7 @@ function StreamContent({ api, rootThread: rootThreadService, toastMessenger }) { ...@@ -68,9 +69,7 @@ function StreamContent({ api, rootThread: rootThreadService, toastMessenger }) {
toastMessenger, toastMessenger,
]); ]);
const rootThread = useStore(store => const rootThread = useRootThread();
rootThreadService.thread(store.getState())
);
return <ThreadList thread={rootThread} />; return <ThreadList thread={rootThread} />;
} }
...@@ -78,10 +77,9 @@ function StreamContent({ api, rootThread: rootThreadService, toastMessenger }) { ...@@ -78,10 +77,9 @@ function StreamContent({ api, rootThread: rootThreadService, toastMessenger }) {
StreamContent.propTypes = { StreamContent.propTypes = {
// Injected services. // Injected services.
api: propTypes.object, api: propTypes.object,
rootThread: propTypes.object,
toastMessenger: propTypes.object, toastMessenger: propTypes.object,
}; };
StreamContent.injectedProps = ['api', 'rootThread', 'toastMessenger']; StreamContent.injectedProps = ['api', 'toastMessenger'];
export default withServices(StreamContent); export default withServices(StreamContent);
...@@ -11,7 +11,7 @@ import AnnotationViewerContent, { ...@@ -11,7 +11,7 @@ import AnnotationViewerContent, {
describe('AnnotationViewerContent', () => { describe('AnnotationViewerContent', () => {
let fakeStore; let fakeStore;
let fakeOnLogin; let fakeOnLogin;
let fakeRootThread; let fakeUseRootThread;
let fakeLoadAnnotationsService; let fakeLoadAnnotationsService;
beforeEach(() => { beforeEach(() => {
...@@ -30,10 +30,11 @@ describe('AnnotationViewerContent', () => { ...@@ -30,10 +30,11 @@ describe('AnnotationViewerContent', () => {
fakeOnLogin = sinon.stub(); fakeOnLogin = sinon.stub();
fakeRootThread = { thread: sinon.stub().returns({}) }; fakeUseRootThread = sinon.stub().returns({});
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'./hooks/use-root-thread': fakeUseRootThread,
'../store/use-store': callback => callback(fakeStore), '../store/use-store': callback => callback(fakeStore),
}); });
}); });
...@@ -47,7 +48,6 @@ describe('AnnotationViewerContent', () => { ...@@ -47,7 +48,6 @@ describe('AnnotationViewerContent', () => {
<AnnotationViewerContent <AnnotationViewerContent
loadAnnotationsService={fakeLoadAnnotationsService} loadAnnotationsService={fakeLoadAnnotationsService}
onLogin={fakeOnLogin} onLogin={fakeOnLogin}
rootThread={fakeRootThread}
{...props} {...props}
/> />
); );
......
...@@ -8,17 +8,15 @@ import { checkAccessibility } from '../../../test-util/accessibility'; ...@@ -8,17 +8,15 @@ import { checkAccessibility } from '../../../test-util/accessibility';
import mockImportedComponents from '../../../test-util/mock-imported-components'; import mockImportedComponents from '../../../test-util/mock-imported-components';
describe('SearchStatusBar', () => { describe('SearchStatusBar', () => {
let fakeRootThread; let fakeUseRootThread;
let fakeStore; let fakeStore;
function createComponent(props) { function createComponent(props) {
return mount(<SearchStatusBar rootThread={fakeRootThread} {...props} />); return mount(<SearchStatusBar {...props} />);
} }
beforeEach(() => { beforeEach(() => {
fakeRootThread = { fakeUseRootThread = sinon.stub().returns({ children: [] });
thread: sinon.stub().returns({ children: [] }),
};
fakeStore = { fakeStore = {
getState: sinon.stub().returns({ getState: sinon.stub().returns({
selection: {}, selection: {},
...@@ -32,6 +30,7 @@ describe('SearchStatusBar', () => { ...@@ -32,6 +30,7 @@ describe('SearchStatusBar', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'./hooks/use-root-thread': fakeUseRootThread,
'../store/use-store': callback => callback(fakeStore), '../store/use-store': callback => callback(fakeStore),
}); });
}); });
...@@ -105,7 +104,7 @@ describe('SearchStatusBar', () => { ...@@ -105,7 +104,7 @@ describe('SearchStatusBar', () => {
}, },
].forEach(test => { ].forEach(test => {
it(test.description, () => { it(test.description, () => {
fakeRootThread.thread.returns({ fakeUseRootThread.returns({
children: test.children, children: test.children,
}); });
...@@ -156,7 +155,7 @@ describe('SearchStatusBar', () => { ...@@ -156,7 +155,7 @@ describe('SearchStatusBar', () => {
}, },
].forEach(test => { ].forEach(test => {
it(test.description, () => { it(test.description, () => {
fakeRootThread.thread.returns({ fakeUseRootThread.returns({
children: test.children, children: test.children,
}); });
const wrapper = createComponent({}); const wrapper = createComponent({});
...@@ -169,7 +168,7 @@ describe('SearchStatusBar', () => { ...@@ -169,7 +168,7 @@ describe('SearchStatusBar', () => {
}); });
it('should not display user-focused mode text if filtered mode is (also) applied', () => { it('should not display user-focused mode text if filtered mode is (also) applied', () => {
fakeRootThread.thread.returns({ fakeUseRootThread.returns({
children: [ children: [
{ id: '1', visible: true, children: [] }, { id: '1', visible: true, children: [] },
{ id: '2', visible: true, children: [] }, { id: '2', visible: true, children: [] },
......
...@@ -10,7 +10,7 @@ import mockImportedComponents from '../../../test-util/mock-imported-components' ...@@ -10,7 +10,7 @@ import mockImportedComponents from '../../../test-util/mock-imported-components'
describe('SidebarContent', () => { describe('SidebarContent', () => {
let fakeFrameSync; let fakeFrameSync;
let fakeLoadAnnotationsService; let fakeLoadAnnotationsService;
let fakeRootThreadService; let fakeUseRootThread;
let fakeStore; let fakeStore;
let fakeStreamer; let fakeStreamer;
let fakeTabsUtil; let fakeTabsUtil;
...@@ -22,7 +22,6 @@ describe('SidebarContent', () => { ...@@ -22,7 +22,6 @@ describe('SidebarContent', () => {
onSignUp={() => null} onSignUp={() => null}
frameSync={fakeFrameSync} frameSync={fakeFrameSync}
loadAnnotationsService={fakeLoadAnnotationsService} loadAnnotationsService={fakeLoadAnnotationsService}
rootThread={fakeRootThreadService}
streamer={fakeStreamer} streamer={fakeStreamer}
{...props} {...props}
/> />
...@@ -36,9 +35,7 @@ describe('SidebarContent', () => { ...@@ -36,9 +35,7 @@ describe('SidebarContent', () => {
fakeLoadAnnotationsService = { fakeLoadAnnotationsService = {
load: sinon.stub(), load: sinon.stub(),
}; };
fakeRootThreadService = { fakeUseRootThread = sinon.stub().returns({});
thread: sinon.stub().returns({}),
};
fakeStreamer = { fakeStreamer = {
connect: sinon.stub(), connect: sinon.stub(),
}; };
...@@ -69,6 +66,7 @@ describe('SidebarContent', () => { ...@@ -69,6 +66,7 @@ describe('SidebarContent', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'./hooks/use-root-thread': fakeUseRootThread,
'../store/use-store': callback => callback(fakeStore), '../store/use-store': callback => callback(fakeStore),
'../util/tabs': fakeTabsUtil, '../util/tabs': fakeTabsUtil,
}); });
......
...@@ -8,7 +8,7 @@ import StreamContent, { $imports } from '../stream-content'; ...@@ -8,7 +8,7 @@ import StreamContent, { $imports } from '../stream-content';
describe('StreamContent', () => { describe('StreamContent', () => {
let fakeApi; let fakeApi;
let fakeRootThread; let fakeUseRootThread;
let fakeSearchFilter; let fakeSearchFilter;
let fakeStore; let fakeStore;
let fakeToastMessenger; let fakeToastMessenger;
...@@ -18,9 +18,7 @@ describe('StreamContent', () => { ...@@ -18,9 +18,7 @@ describe('StreamContent', () => {
search: sinon.stub().resolves({ rows: [], replies: [], total: 0 }), search: sinon.stub().resolves({ rows: [], replies: [], total: 0 }),
}; };
fakeRootThread = { fakeUseRootThread = sinon.stub();
thread: sinon.stub().returns({}),
};
fakeSearchFilter = { fakeSearchFilter = {
toObject: sinon.stub().returns({}), toObject: sinon.stub().returns({}),
...@@ -42,6 +40,7 @@ describe('StreamContent', () => { ...@@ -42,6 +40,7 @@ describe('StreamContent', () => {
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
$imports.$mock({ $imports.$mock({
'./hooks/use-root-thread': fakeUseRootThread,
'../store/use-store': callback => callback(fakeStore), '../store/use-store': callback => callback(fakeStore),
'../util/search-filter': fakeSearchFilter, '../util/search-filter': fakeSearchFilter,
}); });
...@@ -53,11 +52,7 @@ describe('StreamContent', () => { ...@@ -53,11 +52,7 @@ describe('StreamContent', () => {
function createComponent() { function createComponent() {
return mount( return mount(
<StreamContent <StreamContent api={fakeApi} toastMessenger={fakeToastMessenger} />
api={fakeApi}
rootThread={fakeRootThread}
toastMessenger={fakeToastMessenger}
/>
); );
} }
......
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