mirror of
https://github.com/overleaf/overleaf.git
synced 2024-10-24 21:12:38 -04:00
Merge pull request #21283 from overleaf/dp-keyboard-shortcuts
Add missing keyboard shortcuts to new review panel GitOrigin-RevId: 78e3a63284b62c90e8a3803bd81fdf273f1a2ec9
This commit is contained in:
parent
c872d97295
commit
d74981775c
4 changed files with 149 additions and 44 deletions
|
@ -1,12 +1,13 @@
|
|||
import { FC, useCallback } from 'react'
|
||||
import { FC } from 'react'
|
||||
import TrackChangesToggle from '@/features/source-editor/components/review-panel/toolbar/track-changes-toggle'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useTrackChangesStateContext } from '../context/track-changes-state-context'
|
||||
import { postJSON } from '@/infrastructure/fetch-json'
|
||||
import {
|
||||
useTrackChangesStateActionsContext,
|
||||
useTrackChangesStateContext,
|
||||
} from '../context/track-changes-state-context'
|
||||
import { useChangesUsersContext } from '../context/changes-users-context'
|
||||
import { UserId } from '../../../../../types/user'
|
||||
import { buildName } from '../utils/build-name'
|
||||
|
||||
export const ReviewPanelTrackChangesMenu: FC = () => {
|
||||
|
@ -14,34 +15,14 @@ export const ReviewPanelTrackChangesMenu: FC = () => {
|
|||
const permissions = usePermissionsContext()
|
||||
const project = useProjectContext()
|
||||
const trackChanges = useTrackChangesStateContext()
|
||||
const { saveTrackChanges } = useTrackChangesStateActionsContext()
|
||||
const changesUsers = useChangesUsersContext()
|
||||
|
||||
const saveTrackChanges = useCallback(
|
||||
body => {
|
||||
postJSON(`/project/${project._id}/track_changes`, {
|
||||
body,
|
||||
})
|
||||
},
|
||||
[project._id]
|
||||
)
|
||||
|
||||
if (trackChanges === undefined || !changesUsers) {
|
||||
return null
|
||||
}
|
||||
|
||||
const trackChangesIsObject = trackChanges !== true && trackChanges !== false
|
||||
const onForEveryone = trackChanges === true
|
||||
const onForGuests =
|
||||
onForEveryone || (trackChangesIsObject && trackChanges.__guests__ === true)
|
||||
|
||||
const trackChangesValues: Record<UserId, boolean | undefined> = {}
|
||||
if (trackChangesIsObject) {
|
||||
for (const key of Object.keys(trackChanges)) {
|
||||
if (key !== '__guests__') {
|
||||
trackChangesValues[key as UserId] = trackChanges[key as UserId]
|
||||
}
|
||||
}
|
||||
}
|
||||
const { onForEveryone, onForGuests, onForMembers } = trackChanges
|
||||
|
||||
const canToggle = project.features.trackChanges && permissions.write
|
||||
|
||||
|
@ -65,8 +46,7 @@ export const ReviewPanelTrackChangesMenu: FC = () => {
|
|||
const user = changesUsers.get(member._id) ?? member
|
||||
const name = buildName(user)
|
||||
|
||||
const value =
|
||||
trackChanges === true || trackChangesValues[member._id] === true
|
||||
const value = onForEveryone || onForMembers[member._id] === true
|
||||
|
||||
return (
|
||||
<div key={member._id} className="rp-tc-state-item">
|
||||
|
@ -78,7 +58,7 @@ export const ReviewPanelTrackChangesMenu: FC = () => {
|
|||
handleToggle={() => {
|
||||
saveTrackChanges({
|
||||
on_for: {
|
||||
...trackChangesValues,
|
||||
...onForMembers,
|
||||
[member._id]: !value,
|
||||
},
|
||||
on_for_guests: onForGuests,
|
||||
|
@ -99,7 +79,7 @@ export const ReviewPanelTrackChangesMenu: FC = () => {
|
|||
description={t('track_changes_for_guests')}
|
||||
handleToggle={() =>
|
||||
saveTrackChanges({
|
||||
on_for: trackChangesValues,
|
||||
on_for: onForMembers,
|
||||
on_for_guests: !onForGuests,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,43 +1,152 @@
|
|||
import { UserId } from '../../../../../types/user'
|
||||
import { createContext, FC, useContext, useEffect, useState } from 'react'
|
||||
import {
|
||||
createContext,
|
||||
FC,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import useSocketListener from '@/features/ide-react/hooks/use-socket-listener'
|
||||
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
|
||||
import { useUserContext } from '@/shared/context/user-context'
|
||||
import { postJSON } from '@/infrastructure/fetch-json'
|
||||
import useEventListener from '@/shared/hooks/use-event-listener'
|
||||
import { ProjectContextValue } from '@/shared/context/types/project-context'
|
||||
import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
|
||||
|
||||
export type TrackChangesState = boolean | Record<UserId | '__guests__', boolean>
|
||||
export type TrackChangesState = {
|
||||
onForEveryone: boolean
|
||||
onForGuests: boolean
|
||||
onForMembers: Record<UserId, boolean | undefined>
|
||||
}
|
||||
|
||||
export const TrackChangesStateContext = createContext<
|
||||
TrackChangesState | undefined
|
||||
>(undefined)
|
||||
|
||||
type SaveTrackChangesRequestBody = {
|
||||
on?: boolean
|
||||
on_for?: Record<UserId, boolean | undefined>
|
||||
on_for_guests?: boolean
|
||||
}
|
||||
|
||||
type TrackChangesStateActions = {
|
||||
saveTrackChanges: (trackChangesBody: SaveTrackChangesRequestBody) => void
|
||||
}
|
||||
|
||||
const TrackChangesStateActionsContext = createContext<
|
||||
TrackChangesStateActions | undefined
|
||||
>(undefined)
|
||||
|
||||
export const TrackChangesStateProvider: FC = ({ children }) => {
|
||||
const permissions = usePermissionsContext()
|
||||
const { socket } = useConnectionContext()
|
||||
const project = useProjectContext()
|
||||
const user = useUserContext()
|
||||
const { setWantTrackChanges } = useEditorManagerContext()
|
||||
|
||||
// TODO: update project.trackChangesState instead?
|
||||
const [value, setValue] = useState<TrackChangesState>(
|
||||
project.trackChangesState ?? false
|
||||
)
|
||||
const [trackChangesValue, setTrackChangesValue] = useState<
|
||||
ProjectContextValue['trackChangesState']
|
||||
>(project.trackChangesState ?? false)
|
||||
|
||||
useSocketListener(socket, 'toggle-track-changes', setValue)
|
||||
useSocketListener(socket, 'toggle-track-changes', setTrackChangesValue)
|
||||
|
||||
useEffect(() => {
|
||||
setWantTrackChanges(
|
||||
value === true || (value !== false && value[user.id ?? '__guests__'])
|
||||
trackChangesValue === true ||
|
||||
(trackChangesValue !== false &&
|
||||
trackChangesValue[user.id ?? '__guests__'])
|
||||
)
|
||||
}, [setWantTrackChanges, value, user.id])
|
||||
}, [setWantTrackChanges, trackChangesValue, user.id])
|
||||
|
||||
const actions = useMemo(
|
||||
() => ({
|
||||
async saveTrackChanges(trackChangesBody: SaveTrackChangesRequestBody) {
|
||||
postJSON(`/project/${project._id}/track_changes`, {
|
||||
body: trackChangesBody,
|
||||
})
|
||||
},
|
||||
}),
|
||||
[project._id]
|
||||
)
|
||||
|
||||
const trackChangesIsObject =
|
||||
trackChangesValue !== true && trackChangesValue !== false
|
||||
const onForEveryone = trackChangesValue === true
|
||||
const onForGuests =
|
||||
onForEveryone ||
|
||||
(trackChangesIsObject && trackChangesValue.__guests__ === true)
|
||||
|
||||
const onForMembers = useMemo(() => {
|
||||
const onForMembers: Record<UserId, boolean | undefined> = {}
|
||||
if (trackChangesIsObject) {
|
||||
for (const key of Object.keys(trackChangesValue)) {
|
||||
if (key !== '__guests__') {
|
||||
onForMembers[key as UserId] = trackChangesValue[key as UserId]
|
||||
}
|
||||
}
|
||||
}
|
||||
return onForMembers
|
||||
}, [trackChangesIsObject, trackChangesValue])
|
||||
|
||||
useEventListener(
|
||||
'toggle-track-changes',
|
||||
useCallback(() => {
|
||||
if (
|
||||
user.id &&
|
||||
project.features.trackChanges &&
|
||||
permissions.write &&
|
||||
!onForEveryone
|
||||
) {
|
||||
const value = onForMembers[user.id]
|
||||
actions.saveTrackChanges({
|
||||
on_for: {
|
||||
...onForMembers,
|
||||
[user.id]: !value,
|
||||
},
|
||||
on_for_guests: onForGuests,
|
||||
})
|
||||
}
|
||||
}, [
|
||||
actions,
|
||||
onForMembers,
|
||||
onForGuests,
|
||||
onForEveryone,
|
||||
permissions.write,
|
||||
project.features.trackChanges,
|
||||
user.id,
|
||||
])
|
||||
)
|
||||
|
||||
const value = useMemo(
|
||||
() => ({ onForEveryone, onForGuests, onForMembers }),
|
||||
[onForEveryone, onForGuests, onForMembers]
|
||||
)
|
||||
|
||||
return (
|
||||
<TrackChangesStateContext.Provider value={value}>
|
||||
{children}
|
||||
</TrackChangesStateContext.Provider>
|
||||
<TrackChangesStateActionsContext.Provider value={actions}>
|
||||
<TrackChangesStateContext.Provider value={value}>
|
||||
{children}
|
||||
</TrackChangesStateContext.Provider>
|
||||
</TrackChangesStateActionsContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useTrackChangesStateContext = () => {
|
||||
return useContext(TrackChangesStateContext)
|
||||
}
|
||||
|
||||
export const useTrackChangesStateActionsContext = () => {
|
||||
const context = useContext(TrackChangesStateActionsContext)
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useTrackChangesStateActionsContext is only available inside TrackChangesStateProvider'
|
||||
)
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
|
|
@ -26,7 +26,12 @@ import {
|
|||
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
|
||||
|
||||
const toggleReviewPanel = () => {
|
||||
dispatchEditorEvent('toggle-review-panel')
|
||||
if (isSplitTestEnabled('review-panel-redesign')) {
|
||||
window.dispatchEvent(new Event('ui.toggle-review-panel'))
|
||||
} else {
|
||||
dispatchEditorEvent('toggle-review-panel')
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -40,7 +45,11 @@ const addNewCommentFromKbdShortcut = (view: EditorView) => {
|
|||
}
|
||||
|
||||
const toggleTrackChangesFromKbdShortcut = () => {
|
||||
dispatchEditorEvent('toggle-track-changes')
|
||||
if (isSplitTestEnabled('review-panel-redesign')) {
|
||||
window.dispatchEvent(new Event('toggle-track-changes'))
|
||||
} else {
|
||||
dispatchEditorEvent('toggle-track-changes')
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ export const LayoutProvider: FC = ({ children }) => {
|
|||
|
||||
// whether the review pane is open
|
||||
const [reviewPanelOpen, setReviewPanelOpen] =
|
||||
useScopeValue('ui.reviewPanelOpen')
|
||||
useScopeValue<boolean>('ui.reviewPanelOpen')
|
||||
|
||||
// whether the review pane is collapsed
|
||||
const [miniReviewPanelVisible, setMiniReviewPanelVisible] =
|
||||
|
@ -117,6 +117,13 @@ export const LayoutProvider: FC = ({ children }) => {
|
|||
)
|
||||
)
|
||||
|
||||
useEventListener(
|
||||
'ui.toggle-review-panel',
|
||||
useCallback(() => {
|
||||
setReviewPanelOpen(open => !open)
|
||||
}, [setReviewPanelOpen])
|
||||
)
|
||||
|
||||
// whether to display the editor and preview side-by-side or full-width ("flat")
|
||||
const [pdfLayout, setPdfLayout] = useScopeValue<IdeLayout>('ui.pdfLayout')
|
||||
|
||||
|
|
Loading…
Reference in a new issue