overleaf/services/web/frontend/js/features/review-panel-new/context/track-changes-state-context.tsx
David d74981775c Merge pull request #21283 from overleaf/dp-keyboard-shortcuts
Add missing keyboard shortcuts to new review panel

GitOrigin-RevId: 78e3a63284b62c90e8a3803bd81fdf273f1a2ec9
2024-10-24 08:05:17 +00:00

152 lines
4.4 KiB
TypeScript

import { UserId } from '../../../../../types/user'
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 = {
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 [trackChangesValue, setTrackChangesValue] = useState<
ProjectContextValue['trackChangesState']
>(project.trackChangesState ?? false)
useSocketListener(socket, 'toggle-track-changes', setTrackChangesValue)
useEffect(() => {
setWantTrackChanges(
trackChangesValue === true ||
(trackChangesValue !== false &&
trackChangesValue[user.id ?? '__guests__'])
)
}, [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 (
<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
}