mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #19346 from overleaf/dp-review-panel-empty-state
Add empty state to review panel GitOrigin-RevId: 47d7b676e9868942567fc02db234b0827ac86ba3
This commit is contained in:
parent
fb26087894
commit
0c7a32b4f2
8 changed files with 212 additions and 106 deletions
|
@ -335,6 +335,7 @@ const _ProjectController = {
|
||||||
'pdfjs-40',
|
'pdfjs-40',
|
||||||
'personal-access-token',
|
'personal-access-token',
|
||||||
'revert-file',
|
'revert-file',
|
||||||
|
'review-panel-redesign',
|
||||||
'track-pdf-download',
|
'track-pdf-download',
|
||||||
!anonymous && 'writefull-oauth-promotion',
|
!anonymous && 'writefull-oauth-promotion',
|
||||||
'ieee-stylesheet',
|
'ieee-stylesheet',
|
||||||
|
|
|
@ -849,12 +849,14 @@
|
||||||
"no_borders": "",
|
"no_borders": "",
|
||||||
"no_caption": "",
|
"no_caption": "",
|
||||||
"no_comments": "",
|
"no_comments": "",
|
||||||
|
"no_comments_or_suggestions": "",
|
||||||
"no_existing_password": "",
|
"no_existing_password": "",
|
||||||
"no_folder": "",
|
"no_folder": "",
|
||||||
"no_image_files_found": "",
|
"no_image_files_found": "",
|
||||||
"no_members": "",
|
"no_members": "",
|
||||||
"no_messages": "",
|
"no_messages": "",
|
||||||
"no_new_commits_in_github": "",
|
"no_new_commits_in_github": "",
|
||||||
|
"no_one_has_commented_or_left_any_suggestions_yet": "",
|
||||||
"no_other_projects_found": "",
|
"no_other_projects_found": "",
|
||||||
"no_pdf_error_explanation": "",
|
"no_pdf_error_explanation": "",
|
||||||
"no_pdf_error_reason_no_content": "",
|
"no_pdf_error_reason_no_content": "",
|
||||||
|
|
|
@ -3,37 +3,18 @@ import Container from './container'
|
||||||
import Toolbar from './toolbar/toolbar'
|
import Toolbar from './toolbar/toolbar'
|
||||||
import Nav from './nav'
|
import Nav from './nav'
|
||||||
import Toggler from './toggler'
|
import Toggler from './toggler'
|
||||||
import ChangeEntry from './entries/change-entry'
|
|
||||||
import AggregateChangeEntry from './entries/aggregate-change-entry'
|
|
||||||
import CommentEntry from './entries/comment-entry'
|
|
||||||
import AddCommentEntry from './entries/add-comment-entry'
|
|
||||||
import BulkActionsEntry from './entries/bulk-actions-entry/bulk-actions-entry'
|
|
||||||
import PositionedEntries from './positioned-entries'
|
import PositionedEntries from './positioned-entries'
|
||||||
import { useReviewPanelValueContext } from '../../context/review-panel/review-panel-context'
|
import { useReviewPanelValueContext } from '../../context/review-panel/review-panel-context'
|
||||||
import { useEditorContext } from '../../../../shared/context/editor-context'
|
|
||||||
import useCodeMirrorContentHeight from '../../hooks/use-codemirror-content-height'
|
import useCodeMirrorContentHeight from '../../hooks/use-codemirror-content-height'
|
||||||
import { ReviewPanelEntry } from '../../../../../../types/review-panel/entry'
|
import { ReviewPanelEntry } from '../../../../../../types/review-panel/entry'
|
||||||
import {
|
import { ReviewPanelDocEntries } from '../../../../../../types/review-panel/review-panel'
|
||||||
ReviewPanelDocEntries,
|
import Entry from './entry'
|
||||||
ThreadId,
|
import EmptyState from './empty-state'
|
||||||
} from '../../../../../../types/review-panel/review-panel'
|
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||||
|
|
||||||
const isEntryAThreadId = (
|
|
||||||
entry: keyof ReviewPanelDocEntries
|
|
||||||
): entry is ThreadId => entry !== 'add-comment' && entry !== 'bulk-actions'
|
|
||||||
|
|
||||||
function CurrentFileContainer() {
|
function CurrentFileContainer() {
|
||||||
const {
|
const { entries, openDocId } = useReviewPanelValueContext()
|
||||||
commentThreads,
|
|
||||||
entries,
|
|
||||||
openDocId,
|
|
||||||
permissions,
|
|
||||||
loadingThreads,
|
|
||||||
users,
|
|
||||||
nVisibleSelectedChanges: nChanges,
|
|
||||||
} = useReviewPanelValueContext()
|
|
||||||
const contentHeight = useCodeMirrorContentHeight()
|
const contentHeight = useCodeMirrorContentHeight()
|
||||||
const { isRestrictedTokenMember } = useEditorContext()
|
|
||||||
|
|
||||||
const currentDocEntries =
|
const currentDocEntries =
|
||||||
openDocId && openDocId in entries ? entries[openDocId] : undefined
|
openDocId && openDocId in entries ? entries[openDocId] : undefined
|
||||||
|
@ -44,10 +25,19 @@ function CurrentFileContainer() {
|
||||||
>
|
>
|
||||||
}, [currentDocEntries])
|
}, [currentDocEntries])
|
||||||
|
|
||||||
|
const enableEmptyState = useFeatureFlag('review-panel-redesign')
|
||||||
|
|
||||||
|
const showEmptyState =
|
||||||
|
enableEmptyState &&
|
||||||
|
objectEntries.filter(
|
||||||
|
([key]) => key !== 'add-comment' && key !== 'bulk-actions'
|
||||||
|
).length === 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="rp-current-file-container">
|
<Container className="rp-current-file-container">
|
||||||
<div className="review-panel-tools">
|
<div className="review-panel-tools">
|
||||||
<Toolbar />
|
<Toolbar />
|
||||||
|
{showEmptyState && <EmptyState />}
|
||||||
<Nav />
|
<Nav />
|
||||||
</div>
|
</div>
|
||||||
<Toggler />
|
<Toggler />
|
||||||
|
@ -63,87 +53,7 @@ function CurrentFileContainer() {
|
||||||
>
|
>
|
||||||
{openDocId &&
|
{openDocId &&
|
||||||
objectEntries.map(([id, entry]) => {
|
objectEntries.map(([id, entry]) => {
|
||||||
if (!entry.visible) {
|
return <Entry key={id} id={id} entry={entry} />
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
isEntryAThreadId(id) &&
|
|
||||||
(entry.type === 'insert' || entry.type === 'delete')
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<ChangeEntry
|
|
||||||
key={id}
|
|
||||||
docId={openDocId}
|
|
||||||
entryId={id}
|
|
||||||
permissions={permissions}
|
|
||||||
user={users[entry.metadata.user_id]}
|
|
||||||
content={entry.content}
|
|
||||||
offset={entry.offset}
|
|
||||||
type={entry.type}
|
|
||||||
focused={entry.focused}
|
|
||||||
entryIds={entry.entry_ids}
|
|
||||||
timestamp={entry.metadata.ts}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEntryAThreadId(id) && entry.type === 'aggregate-change') {
|
|
||||||
return (
|
|
||||||
<AggregateChangeEntry
|
|
||||||
key={id}
|
|
||||||
docId={openDocId}
|
|
||||||
entryId={id}
|
|
||||||
permissions={permissions}
|
|
||||||
user={users[entry.metadata.user_id]}
|
|
||||||
content={entry.content}
|
|
||||||
replacedContent={entry.metadata.replaced_content}
|
|
||||||
offset={entry.offset}
|
|
||||||
focused={entry.focused}
|
|
||||||
entryIds={entry.entry_ids}
|
|
||||||
timestamp={entry.metadata.ts}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
isEntryAThreadId(id) &&
|
|
||||||
entry.type === 'comment' &&
|
|
||||||
!loadingThreads
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<CommentEntry
|
|
||||||
key={id}
|
|
||||||
docId={openDocId}
|
|
||||||
threadId={entry.thread_id}
|
|
||||||
thread={commentThreads[entry.thread_id]}
|
|
||||||
entryId={id}
|
|
||||||
offset={entry.offset}
|
|
||||||
focused={entry.focused}
|
|
||||||
permissions={permissions}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
entry.type === 'add-comment' &&
|
|
||||||
permissions.comment &&
|
|
||||||
!isRestrictedTokenMember
|
|
||||||
) {
|
|
||||||
return <AddCommentEntry key={id} />
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.type === 'bulk-actions' && permissions.write) {
|
|
||||||
return (
|
|
||||||
<BulkActionsEntry
|
|
||||||
key={id}
|
|
||||||
entryId={entry.type}
|
|
||||||
nChanges={nChanges}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
})}
|
})}
|
||||||
</PositionedEntries>
|
</PositionedEntries>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
function EmptyState() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rp-empty-state">
|
||||||
|
<div className="rp-empty-state-inner">
|
||||||
|
<div className="rp-empty-state-comment-icon">
|
||||||
|
<MaterialIcon type="question_answer" />
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<strong>{t('no_comments_or_suggestions')}</strong>
|
||||||
|
</p>
|
||||||
|
<p>{t('no_one_has_commented_or_left_any_suggestions_yet')}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EmptyState
|
|
@ -0,0 +1,109 @@
|
||||||
|
import { memo } from 'react'
|
||||||
|
import ChangeEntry from './entries/change-entry'
|
||||||
|
import AggregateChangeEntry from './entries/aggregate-change-entry'
|
||||||
|
import CommentEntry from './entries/comment-entry'
|
||||||
|
import AddCommentEntry from './entries/add-comment-entry'
|
||||||
|
import BulkActionsEntry from './entries/bulk-actions-entry/bulk-actions-entry'
|
||||||
|
import {
|
||||||
|
ReviewPanelDocEntries,
|
||||||
|
ThreadId,
|
||||||
|
} from '../../../../../../types/review-panel/review-panel'
|
||||||
|
import { useReviewPanelValueContext } from '../../context/review-panel/review-panel-context'
|
||||||
|
import { useEditorContext } from '../../../../shared/context/editor-context'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
entry: ReviewPanelDocEntries[keyof ReviewPanelDocEntries]
|
||||||
|
id: ThreadId | 'add-comment' | 'bulk-actions'
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEntryAThreadId = (
|
||||||
|
entry: keyof ReviewPanelDocEntries
|
||||||
|
): entry is ThreadId => entry !== 'add-comment' && entry !== 'bulk-actions'
|
||||||
|
|
||||||
|
function Entry({ entry, id }: Props) {
|
||||||
|
const {
|
||||||
|
commentThreads,
|
||||||
|
openDocId,
|
||||||
|
permissions,
|
||||||
|
loadingThreads,
|
||||||
|
users,
|
||||||
|
nVisibleSelectedChanges: nChanges,
|
||||||
|
} = useReviewPanelValueContext()
|
||||||
|
const { isRestrictedTokenMember } = useEditorContext()
|
||||||
|
|
||||||
|
if (!entry.visible || !openDocId) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isEntryAThreadId(id) &&
|
||||||
|
(entry.type === 'insert' || entry.type === 'delete')
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<ChangeEntry
|
||||||
|
key={id}
|
||||||
|
docId={openDocId}
|
||||||
|
entryId={id}
|
||||||
|
permissions={permissions}
|
||||||
|
user={users[entry.metadata.user_id]}
|
||||||
|
content={entry.content}
|
||||||
|
offset={entry.offset}
|
||||||
|
type={entry.type}
|
||||||
|
focused={entry.focused}
|
||||||
|
entryIds={entry.entry_ids}
|
||||||
|
timestamp={entry.metadata.ts}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEntryAThreadId(id) && entry.type === 'aggregate-change') {
|
||||||
|
return (
|
||||||
|
<AggregateChangeEntry
|
||||||
|
key={id}
|
||||||
|
docId={openDocId}
|
||||||
|
entryId={id}
|
||||||
|
permissions={permissions}
|
||||||
|
user={users[entry.metadata.user_id]}
|
||||||
|
content={entry.content}
|
||||||
|
replacedContent={entry.metadata.replaced_content}
|
||||||
|
offset={entry.offset}
|
||||||
|
focused={entry.focused}
|
||||||
|
entryIds={entry.entry_ids}
|
||||||
|
timestamp={entry.metadata.ts}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEntryAThreadId(id) && entry.type === 'comment' && !loadingThreads) {
|
||||||
|
return (
|
||||||
|
<CommentEntry
|
||||||
|
key={id}
|
||||||
|
docId={openDocId}
|
||||||
|
threadId={entry.thread_id}
|
||||||
|
thread={commentThreads[entry.thread_id]}
|
||||||
|
entryId={id}
|
||||||
|
offset={entry.offset}
|
||||||
|
focused={entry.focused}
|
||||||
|
permissions={permissions}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
entry.type === 'add-comment' &&
|
||||||
|
permissions.comment &&
|
||||||
|
!isRestrictedTokenMember
|
||||||
|
) {
|
||||||
|
return <AddCommentEntry key={id} />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.type === 'bulk-actions' && permissions.write) {
|
||||||
|
return (
|
||||||
|
<BulkActionsEntry key={id} entryId={entry.type} nChanges={nChanges} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(Entry)
|
|
@ -6,15 +6,33 @@ import Icon from '../../../../shared/components/icon'
|
||||||
import OverviewFile from './overview-file'
|
import OverviewFile from './overview-file'
|
||||||
import { useReviewPanelValueContext } from '../../context/review-panel/review-panel-context'
|
import { useReviewPanelValueContext } from '../../context/review-panel/review-panel-context'
|
||||||
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
|
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
|
||||||
import { memo } from 'react'
|
import { memo, useMemo } from 'react'
|
||||||
|
import EmptyState from './empty-state'
|
||||||
|
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||||
|
|
||||||
function OverviewContainer() {
|
function OverviewContainer() {
|
||||||
|
const { entries } = useReviewPanelValueContext()
|
||||||
|
|
||||||
const { isOverviewLoading } = useReviewPanelValueContext()
|
const { isOverviewLoading } = useReviewPanelValueContext()
|
||||||
const { docs } = useFileTreeData()
|
const { docs } = useFileTreeData()
|
||||||
|
|
||||||
|
const entryCount = useMemo(() => {
|
||||||
|
return docs
|
||||||
|
?.map(doc => {
|
||||||
|
const docEntries = entries[doc.doc.id] ?? {}
|
||||||
|
return Object.keys(docEntries).filter(
|
||||||
|
key => key !== 'add-comment' && key !== 'bulk-actions'
|
||||||
|
).length
|
||||||
|
})
|
||||||
|
.reduce((acc, curr) => acc + curr, 0)
|
||||||
|
}, [docs, entries])
|
||||||
|
|
||||||
|
const enableEmptyState = useFeatureFlag('review-panel-redesign')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Toggler />
|
<Toggler />
|
||||||
|
{enableEmptyState && entryCount === 0 && <EmptyState />}
|
||||||
<Toolbar />
|
<Toolbar />
|
||||||
<div
|
<div
|
||||||
className="rp-entry-list"
|
className="rp-entry-list"
|
||||||
|
|
|
@ -1231,3 +1231,45 @@ button when (@is-overleaf-light = true) {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rp-empty-state {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
.rp-empty-state-inner {
|
||||||
|
position: sticky;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rp-empty-state-comment-icon {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.material-symbols {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1243,6 +1243,7 @@
|
||||||
"no_borders": "No borders",
|
"no_borders": "No borders",
|
||||||
"no_caption": "No caption",
|
"no_caption": "No caption",
|
||||||
"no_comments": "No comments",
|
"no_comments": "No comments",
|
||||||
|
"no_comments_or_suggestions": "No comments or suggestions",
|
||||||
"no_existing_password": "Please use the password reset form to set your password",
|
"no_existing_password": "Please use the password reset form to set your password",
|
||||||
"no_featured_templates": "No featured templates",
|
"no_featured_templates": "No featured templates",
|
||||||
"no_folder": "No folder",
|
"no_folder": "No folder",
|
||||||
|
@ -1251,6 +1252,7 @@
|
||||||
"no_members": "No members",
|
"no_members": "No members",
|
||||||
"no_messages": "No messages",
|
"no_messages": "No messages",
|
||||||
"no_new_commits_in_github": "No new commits in GitHub since last merge.",
|
"no_new_commits_in_github": "No new commits in GitHub since last merge.",
|
||||||
|
"no_one_has_commented_or_left_any_suggestions_yet": "No one has commented or left any suggestions yet.",
|
||||||
"no_other_projects_found": "No other projects found, please create another project first",
|
"no_other_projects_found": "No other projects found, please create another project first",
|
||||||
"no_other_sessions": "No other sessions active",
|
"no_other_sessions": "No other sessions active",
|
||||||
"no_pdf_error_explanation": "This compile didn’t produce a PDF. This can happen if:",
|
"no_pdf_error_explanation": "This compile didn’t produce a PDF. This can happen if:",
|
||||||
|
|
Loading…
Reference in a new issue