diff --git a/web/modals/history/history-modal.react.js b/web/modals/history/history-modal.react.js index af8117a94..6dc320bb8 100644 --- a/web/modals/history/history-modal.react.js +++ b/web/modals/history/history-modal.react.js @@ -1,287 +1,295 @@ // @flow import classNames from 'classnames'; import invariant from 'invariant'; import _filter from 'lodash/fp/filter.js'; import _flow from 'lodash/fp/flow.js'; import _map from 'lodash/fp/map.js'; import _unionBy from 'lodash/fp/unionBy.js'; import * as React from 'react'; import { fetchEntriesActionTypes, useFetchEntries, fetchRevisionsForEntryActionTypes, useFetchRevisionsForEntry, } from 'lib/actions/entry-actions.js'; import type { FetchRevisionsForEntryInput } from 'lib/actions/entry-actions.js'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import { nonExcludeDeletedCalendarFiltersSelector } from 'lib/selectors/calendar-filter-selectors.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import type { EntryInfo, CalendarQuery, FetchEntryInfosResult, FetchRevisionsForEntryPayload, } from 'lib/types/entry-types.js'; import { type CalendarFilter } from 'lib/types/filter-types.js'; import type { HistoryMode, HistoryRevisionInfo, } from 'lib/types/history-types.js'; import type { LoadingStatus } from 'lib/types/loading-types.js'; +import type { RawThreadInfos } from 'lib/types/thread-types.js'; import { prettyDateWithoutDay } from 'lib/utils/date-utils.js'; import { useDispatchActionPromise, type DispatchActionPromise, } from 'lib/utils/redux-promise-utils.js'; import HistoryEntry from './history-entry.react.js'; import HistoryRevision from './history-revision.react.js'; import css from './history.css'; import LoadingIndicator from '../../loading-indicator.react.js'; import { useSelector } from '../../redux/redux-utils.js'; import { allDaysToEntries } from '../../selectors/entry-selectors.js'; import Modal from '../modal.react.js'; type BaseProps = { +mode: HistoryMode, +dayString: string, +currentEntryID?: ?string, }; type Props = { ...BaseProps, +entryInfos: ?(EntryInfo[]), + +threadInfos: ?RawThreadInfos, +dayLoadingStatus: LoadingStatus, +entryLoadingStatus: LoadingStatus, +calendarFilters: $ReadOnlyArray, +dispatchActionPromise: DispatchActionPromise, +fetchEntries: ( calendarQuery: CalendarQuery, ) => Promise, +fetchRevisionsForEntry: ( input: FetchRevisionsForEntryInput, ) => Promise<$ReadOnlyArray>, +onClose: () => void, }; type State = { +mode: HistoryMode, +animateModeChange: boolean, +currentEntryID: ?string, +revisions: $ReadOnlyArray, }; class HistoryModal extends React.PureComponent { static defaultProps: Partial = { currentEntryID: null }; constructor(props: Props) { super(props); this.state = { mode: props.mode, animateModeChange: false, currentEntryID: props.currentEntryID, revisions: [], }; } componentDidMount() { this.loadDay(); if (this.state.mode === 'entry') { invariant(this.state.currentEntryID, 'entry ID should be set'); this.loadEntry(this.state.currentEntryID); } } render(): React.Node { let allHistoryButton = null; if (this.state.mode === 'entry') { allHistoryButton = ( < all entries ); } const prettyDate = prettyDateWithoutDay(this.props.dayString); const loadingStatus = this.state.mode === 'day' ? this.props.dayLoadingStatus : this.props.entryLoadingStatus; let entries; const entryInfos = this.props.entryInfos; + const threadInfos = this.props.threadInfos; if (entryInfos) { entries = _flow( - _filter((entryInfo: EntryInfo) => entryInfo.id), + _filter( + (entryInfo: EntryInfo) => + entryInfo.id && !threadInfos?.[entryInfo.threadID].thick, + ), _map((entryInfo: EntryInfo) => { const serverID = entryInfo.id; invariant(serverID, 'serverID should be set'); return ( ); }), )(entryInfos); } else { entries = []; } const revisionInfos = this.state.revisions.filter( revisionInfo => revisionInfo.entryID === this.state.currentEntryID, ); const revisions = []; for (let i = 0; i < revisionInfos.length; i++) { const revisionInfo = revisionInfos[i]; const nextRevisionInfo = revisionInfos[i + 1]; const isDeletionOrRestoration = nextRevisionInfo !== undefined && revisionInfo.deleted !== nextRevisionInfo.deleted; revisions.push( , ); } const animate = this.state.animateModeChange; const dayMode = this.state.mode === 'day'; const dayClasses = classNames({ [css.dayHistory]: true, [css.dayHistoryVisible]: dayMode && !animate, [css.dayHistoryInvisible]: !dayMode && !animate, [css.dayHistoryVisibleAnimate]: dayMode && animate, [css.dayHistoryInvisibleAnimate]: !dayMode && animate, }); const entryMode = this.state.mode === 'entry'; const entryClasses = classNames({ [css.entryHistory]: true, [css.entryHistoryVisible]: entryMode && !animate, [css.entryHistoryInvisible]: !entryMode && !animate, [css.entryHistoryVisibleAnimate]: entryMode && animate, [css.entryHistoryInvisibleAnimate]: !entryMode && animate, }); return (
{allHistoryButton} {prettyDate}
    {entries}
    {revisions}
); } loadDay() { void this.props.dispatchActionPromise( fetchEntriesActionTypes, this.props.fetchEntries({ startDate: this.props.dayString, endDate: this.props.dayString, filters: this.props.calendarFilters, }), ); } loadEntry(entryID: string) { this.setState({ mode: 'entry', currentEntryID: entryID }); void this.props.dispatchActionPromise( fetchRevisionsForEntryActionTypes, this.fetchRevisionsForEntryAction(entryID), ); } async fetchRevisionsForEntryAction( entryID: string, ): Promise { const result = await this.props.fetchRevisionsForEntry({ entryID }); this.setState(prevState => { // This merge here will preserve time ordering correctly const revisions = _unionBy('id')(result)(prevState.revisions); return { ...prevState, revisions }; }); return { entryID, text: result[0].text, deleted: result[0].deleted, }; } onClickEntry = (entryID: string) => { this.setState({ animateModeChange: true }); this.loadEntry(entryID); }; onClickAllEntries = (event: SyntheticEvent) => { event.preventDefault(); this.setState({ mode: 'day', animateModeChange: true, }); }; animateAndLoadEntry = (entryID: string) => { this.setState({ animateModeChange: true }); this.loadEntry(entryID); }; } const dayLoadingStatusSelector = createLoadingStatusSelector( fetchEntriesActionTypes, ); const entryLoadingStatusSelector = createLoadingStatusSelector( fetchRevisionsForEntryActionTypes, ); const ConnectedHistoryModal: React.ComponentType = React.memo(function ConnectedHistoryModal(props) { const entryInfos = useSelector( state => allDaysToEntries(state)[props.dayString], ); + const threadInfos = useSelector(state => state.threadStore.threadInfos); const dayLoadingStatus = useSelector(dayLoadingStatusSelector); const entryLoadingStatus = useSelector(entryLoadingStatusSelector); const calendarFilters = useSelector( nonExcludeDeletedCalendarFiltersSelector, ); const callFetchEntries = useFetchEntries(); const callFetchRevisionsForEntry = useFetchRevisionsForEntry(); const dispatchActionPromise = useDispatchActionPromise(); const modalContext = useModalContext(); return ( ); }); export default ConnectedHistoryModal;