Merge pull request #20617 from overleaf/dk-review-indicator-hover

Improve "mini" view popovers for new review panel

GitOrigin-RevId: ff5fc0af70bd9660d5cc17437b25824ef4c9a704
This commit is contained in:
David 2024-10-02 12:58:46 +01:00 committed by Copybot
parent 46198cd780
commit 56d396a6cf
6 changed files with 204 additions and 136 deletions

View file

@ -23,7 +23,21 @@ export const ReviewPanelChange = memo<{
editable?: boolean editable?: boolean
docId: string docId: string
hoverRanges?: boolean hoverRanges?: boolean
}>(({ change, aggregate, top, docId, hoverRanges, editable = true }) => { hovered?: boolean
onEnter?: () => void
onLeave?: () => void
}>(
({
change,
aggregate,
top,
docId,
hoverRanges,
editable = true,
hovered,
onEnter,
onLeave,
}) => {
const { t } = useTranslation() const { t } = useTranslation()
const { acceptChanges, rejectChanges } = useRangesActionsContext() const { acceptChanges, rejectChanges } = useRangesActionsContext()
const permissions = usePermissionsContext() const permissions = usePermissionsContext()
@ -39,6 +53,7 @@ export const ReviewPanelChange = memo<{
className={classnames('review-panel-entry-change', { className={classnames('review-panel-entry-change', {
'review-panel-entry-insert': 'i' in change.op, 'review-panel-entry-insert': 'i' in change.op,
'review-panel-entry-delete': 'd' in change.op, 'review-panel-entry-delete': 'd' in change.op,
'review-panel-entry-hover': hovered,
// TODO: aggregate // TODO: aggregate
})} })}
top={top} top={top}
@ -47,11 +62,19 @@ export const ReviewPanelChange = memo<{
docId={docId} docId={docId}
hoverRanges={hoverRanges} hoverRanges={hoverRanges}
> >
<div className="review-panel-entry-indicator"> <div
className="review-panel-entry-indicator"
onMouseEnter={onEnter}
onMouseLeave={onLeave}
>
<MaterialIcon type="edit" className="review-panel-entry-icon" /> <MaterialIcon type="edit" className="review-panel-entry-icon" />
</div> </div>
<div className="review-panel-entry-content"> <div
className="review-panel-entry-content"
onMouseEnter={onEnter}
onMouseLeave={onLeave}
>
<div className="review-panel-entry-header"> <div className="review-panel-entry-header">
<div> <div>
<div className="review-panel-entry-user"> <div className="review-panel-entry-user">
@ -67,6 +90,7 @@ export const ReviewPanelChange = memo<{
id="accept-change" id="accept-change"
overlayProps={{ placement: 'bottom' }} overlayProps={{ placement: 'bottom' }}
description={t('accept_change')} description={t('accept_change')}
tooltipProps={{ className: 'review-panel-tooltip' }}
> >
<Button <Button
onClick={() => onClick={() =>
@ -88,6 +112,7 @@ export const ReviewPanelChange = memo<{
id="reject-change" id="reject-change"
description={t('reject_change')} description={t('reject_change')}
overlayProps={{ placement: 'bottom' }} overlayProps={{ placement: 'bottom' }}
tooltipProps={{ className: 'review-panel-tooltip' }}
> >
<Button <Button
bsStyle={null} bsStyle={null}
@ -164,5 +189,6 @@ export const ReviewPanelChange = memo<{
</div> </div>
</ReviewPanelEntry> </ReviewPanelEntry>
) )
}) }
)
ReviewPanelChange.displayName = 'ReviewPanelChange' ReviewPanelChange.displayName = 'ReviewPanelChange'

View file

@ -14,7 +14,9 @@ import useSubmittableTextInput from '../hooks/use-submittable-text-input'
export const ReviewPanelCommentContent = memo<{ export const ReviewPanelCommentContent = memo<{
comment: Change<CommentOperation> comment: Change<CommentOperation>
isResolved: boolean isResolved: boolean
}>(({ comment, isResolved }) => { onLeave?: () => void
onEnter?: () => void
}>(({ comment, isResolved, onLeave, onEnter }) => {
const { t } = useTranslation() const { t } = useTranslation()
const [submitting, setSubmitting] = useState(false) const [submitting, setSubmitting] = useState(false)
const [error, setError] = useState<Error>() const [error, setError] = useState<Error>()
@ -47,7 +49,11 @@ export const ReviewPanelCommentContent = memo<{
} }
return ( return (
<div className="review-panel-entry-content"> <div
className="review-panel-entry-content"
onMouseEnter={onEnter}
onMouseLeave={onLeave}
>
{thread.messages.map((message, i) => { {thread.messages.map((message, i) => {
const isReply = i !== 0 const isReply = i !== 0

View file

@ -11,7 +11,10 @@ export const ReviewPanelComment = memo<{
docId: string docId: string
top?: number top?: number
hoverRanges?: boolean hoverRanges?: boolean
}>(({ comment, top, docId, hoverRanges }) => { onEnter?: () => void
onLeave?: () => void
hovered?: boolean
}>(({ comment, top, hovered, onEnter, onLeave, docId, hoverRanges }) => {
const threads = useThreadsContext() const threads = useThreadsContext()
const thread = threads?.[comment.op.t] const thread = threads?.[comment.op.t]
@ -23,6 +26,7 @@ export const ReviewPanelComment = memo<{
<ReviewPanelEntry <ReviewPanelEntry
className={classnames('review-panel-entry-comment', { className={classnames('review-panel-entry-comment', {
'review-panel-entry-loaded': !!threads?.[comment.op.t], 'review-panel-entry-loaded': !!threads?.[comment.op.t],
'review-panel-entry-hover': hovered,
})} })}
docId={docId} docId={docId}
top={top} top={top}
@ -30,10 +34,19 @@ export const ReviewPanelComment = memo<{
position={comment.op.p} position={comment.op.p}
hoverRanges={hoverRanges} hoverRanges={hoverRanges}
> >
<div className="review-panel-entry-indicator"> <div
className="review-panel-entry-indicator"
onMouseEnter={onEnter}
onMouseLeave={onLeave}
>
<MaterialIcon type="comment" className="review-panel-entry-icon" /> <MaterialIcon type="comment" className="review-panel-entry-icon" />
</div> </div>
<ReviewPanelCommentContent comment={comment} isResolved={false} /> <ReviewPanelCommentContent
comment={comment}
isResolved={false}
onLeave={onLeave}
onEnter={onEnter}
/>
</ReviewPanelEntry> </ReviewPanelEntry>
) )
}) })

View file

@ -43,6 +43,20 @@ const ReviewPanelCurrentFile: FC = () => {
const ranges = useRangesContext() const ranges = useRangesContext()
const threads = useThreadsContext() const threads = useThreadsContext()
const state = useCodeMirrorStateContext() const state = useCodeMirrorStateContext()
const [hoveredEntry, setHoveredEntry] = useState<string | null>(null)
const hoverTimeout = useRef<number>(0)
const handleEntryEnter = useCallback((id: string) => {
clearTimeout(hoverTimeout.current)
setHoveredEntry(id)
}, [])
const handleEntryLeave = useCallback((id: string) => {
clearTimeout(hoverTimeout.current)
hoverTimeout.current = window.setTimeout(() => {
setHoveredEntry(null)
}, 100)
}, [])
const [aggregatedRanges, setAggregatedRanges] = useState<AggregatedRanges>() const [aggregatedRanges, setAggregatedRanges] = useState<AggregatedRanges>()
@ -298,6 +312,9 @@ const ReviewPanelCurrentFile: FC = () => {
change={change} change={change}
top={positions.get(change.id)} top={positions.get(change.id)}
aggregate={aggregatedRanges.aggregates.get(change.id)} aggregate={aggregatedRanges.aggregates.get(change.id)}
hovered={hoveredEntry === change.id}
onEnter={() => handleEntryEnter(change.id)}
onLeave={() => handleEntryLeave(change.id)}
/> />
) )
)} )}
@ -310,6 +327,9 @@ const ReviewPanelCurrentFile: FC = () => {
key={comment.id} key={comment.id}
comment={comment} comment={comment}
top={positions.get(comment.id)} top={positions.get(comment.id)}
hovered={hoveredEntry === comment.id}
onEnter={() => handleEntryEnter(comment.id)}
onLeave={() => handleEntryLeave(comment.id)}
/> />
) )
)} )}

View file

@ -106,6 +106,7 @@ export const ReviewPanelMessage: FC<{
id="resolve-thread" id="resolve-thread"
overlayProps={{ placement: 'bottom' }} overlayProps={{ placement: 'bottom' }}
description={t('resolve_comment')} description={t('resolve_comment')}
tooltipProps={{ className: 'review-panel-tooltip' }}
> >
<Button onClick={onResolve} bsStyle={null}> <Button onClick={onResolve} bsStyle={null}>
<MaterialIcon <MaterialIcon

View file

@ -554,15 +554,14 @@
margin-left: 0; margin-left: 0;
background-color: transparent; background-color: transparent;
border: none; border: none;
width: 100%;
} }
.review-panel-entry-indicator { .review-panel-entry-indicator {
position: absolute; position: absolute;
left: 0; left: 0;
top: 7px; top: 0;
display: flex; display: flex;
width: 16px;
height: 16px;
color: @content-secondary; color: @content-secondary;
cursor: pointer; cursor: pointer;
} }
@ -576,7 +575,7 @@
padding: @spacing-02; padding: @spacing-02;
} }
.review-panel-entry:hover { .review-panel-entry-hover {
.review-panel-entry-content { .review-panel-entry-content {
display: initial; display: initial;
position: absolute; position: absolute;
@ -599,3 +598,6 @@
align-items: center; align-items: center;
gap: 2px; gap: 2px;
} }
.review-panel-tooltip {
pointer-events: none; // this is to prevent mouseLeave event from firing when hovering over the tooltip
}