first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View File

@@ -0,0 +1,151 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useCodeMirrorViewContext } from '@/features/source-editor/components/codemirror-editor'
import {
Change,
CommentOperation,
EditOperation,
} from '../../../../../types/change'
import { DecorationSet, EditorView } from '@codemirror/view'
import { EditorSelection } from '@codemirror/state'
import _ from 'lodash'
import useReviewPanelLayout from './use-review-panel-layout'
const useMoreCommments = (
changes: Change<EditOperation>[],
comments: Change<CommentOperation>[],
newComments: DecorationSet
): {
onEntriesPositioned: () => void
onMoreCommentsAboveClick: null | (() => void)
onMoreCommentsBelowClick: null | (() => void)
} => {
const view = useCodeMirrorViewContext()
const { showPanel, mini } = useReviewPanelLayout()
const reviewPanelOpen = showPanel && !mini
const [positionAbove, setPositionAbove] = useState<number | null>(null)
const [positionBelow, setPositionBelow] = useState<number | null>(null)
const updateEntryPositions = useMemo(
() =>
_.debounce(
() =>
view.requestMeasure({
key: 'review-panel-more-comments',
read(view) {
const container = view.scrollDOM
if (!container || !reviewPanelOpen) {
return { positionAbove: null, positionBelow: null }
}
const containerTop = container.scrollTop
const containerBottom = containerTop + container.clientHeight
// First check for any entries in view by looking for the actual rendered entries
for (const entryElt of container.querySelectorAll<HTMLElement>(
'.review-panel-entry'
)) {
const entryTop = entryElt?.offsetTop ?? 0
const entryBottom = entryTop + (entryElt?.offsetHeight ?? 0)
if (entryBottom > containerTop && entryTop < containerBottom) {
// Some part of the entry is in view
return { positionAbove: null, positionBelow: null }
}
}
// Find the max and min positions in the visible part of the viewport
const visibleFrom = view.lineBlockAtHeight(containerTop).from
const visibleTo = view.lineBlockAtHeight(containerBottom).to
// Then go through the positions to find the first entry above and below the visible viewport.
// We can't use the rendered entries for this because only the entries that are in the viewport (or
// have been in the viewport during the current page view session) are actually rendered.
let firstEntryAbove: number | null = null
let firstEntryBelow: number | null = null
const updateFirstEntryAboveBelowPositions = (
position: number
) => {
if (visibleFrom === null || position < visibleFrom) {
firstEntryAbove = Math.max(firstEntryAbove ?? 0, position)
}
if (visibleTo === null || position > visibleTo) {
firstEntryBelow = Math.min(
firstEntryBelow ?? Number.MAX_VALUE,
position
)
}
}
for (const entry of [...changes, ...comments]) {
updateFirstEntryAboveBelowPositions(entry.op.p)
}
const cursor = newComments.iter()
while (cursor.value) {
updateFirstEntryAboveBelowPositions(cursor.from)
cursor.next()
}
return {
positionAbove: firstEntryAbove,
positionBelow: firstEntryBelow,
}
},
write({ positionAbove, positionBelow }) {
setPositionAbove(positionAbove)
setPositionBelow(positionBelow)
},
}),
200
),
[changes, comments, newComments, view, reviewPanelOpen]
)
useEffect(() => {
const scrollerElt = document.getElementsByClassName('cm-scroller')[0]
if (scrollerElt) {
scrollerElt.addEventListener('scroll', updateEntryPositions)
return () => {
scrollerElt.removeEventListener('scroll', updateEntryPositions)
}
}
}, [updateEntryPositions])
const onMoreCommentsClick = useCallback(
(position: number) => {
view.dispatch({
effects: EditorView.scrollIntoView(position, {
y: 'center',
}),
selection: EditorSelection.cursor(position),
})
},
[view]
)
const onMoreCommentsAboveClick = useCallback(() => {
if (positionAbove !== null) {
onMoreCommentsClick(positionAbove)
}
}, [positionAbove, onMoreCommentsClick])
const onMoreCommentsBelowClick = useCallback(() => {
if (positionBelow !== null) {
onMoreCommentsClick(positionBelow)
}
}, [positionBelow, onMoreCommentsClick])
return {
onEntriesPositioned: updateEntryPositions,
onMoreCommentsAboveClick:
positionAbove !== null ? onMoreCommentsAboveClick : null,
onMoreCommentsBelowClick:
positionBelow !== null ? onMoreCommentsBelowClick : null,
}
}
export default useMoreCommments

View File

@@ -0,0 +1,22 @@
import { useCallback } from 'react'
import { DocId } from '../../../../../types/project-settings'
import { useProjectContext } from '../../../shared/context/project-context'
import usePersistedState from '../../../shared/hooks/use-persisted-state'
export default function useOverviewFileCollapsed(docId: DocId) {
const { _id: projectId } = useProjectContext()
const [collapsedDocs, setCollapsedDocs] = usePersistedState<
Record<DocId, boolean>
>(`docs_collapsed_state:${projectId}`, {}, false, true)
const toggleCollapsed = useCallback(() => {
setCollapsedDocs((collapsedDocs: Record<DocId, boolean>) => {
return {
...collapsedDocs,
[docId]: !collapsedDocs[docId],
}
})
}, [docId, setCollapsedDocs])
return { collapsed: collapsedDocs[docId], toggleCollapsed }
}

View File

@@ -0,0 +1,64 @@
import { useCallback, useEffect, useState } from 'react'
import { Ranges } from '../context/ranges-context'
import { useProjectContext } from '@/shared/context/project-context'
import { getJSON } from '@/infrastructure/fetch-json'
import useSocketListener from '@/features/ide-react/hooks/use-socket-listener'
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
export default function useProjectRanges() {
const { _id: projectId } = useProjectContext()
const [error, setError] = useState<Error>()
const [projectRanges, setProjectRanges] = useState<Map<string, Ranges>>()
const [loading, setLoading] = useState(true)
const { socket } = useConnectionContext()
useEffect(() => {
setLoading(true)
getJSON<{ id: string; ranges: Ranges }[]>(`/project/${projectId}/ranges`)
.then(data => {
setProjectRanges(
new Map(
data.map(item => [
item.id,
{
docId: item.id,
changes: item.ranges.changes ?? [],
comments: item.ranges.comments ?? [],
},
])
)
)
})
.catch(error => setError(error))
.finally(() => setLoading(false))
}, [projectId])
useSocketListener(
socket,
'accept-changes',
useCallback((docId: string, entryIds: string[]) => {
setProjectRanges(prevProjectRanges => {
if (!prevProjectRanges) {
return prevProjectRanges
}
const ranges = prevProjectRanges.get(docId)
if (!ranges) {
return prevProjectRanges
}
const updatedProjectRanges = new Map(prevProjectRanges)
updatedProjectRanges.set(docId, {
...ranges,
changes: ranges.changes.filter(
change => !entryIds.includes(change.id)
),
})
return updatedProjectRanges
})
}, [])
)
return { projectRanges, error, loading }
}

View File

@@ -0,0 +1,30 @@
import { useLayoutContext } from '@/shared/context/layout-context'
import { useRangesContext } from '../context/ranges-context'
import { useThreadsContext } from '@/features/review-panel-new/context/threads-context'
import { hasActiveRange } from '@/features/review-panel-new/utils/has-active-range'
import { useRailContext } from '@/features/ide-redesign/contexts/rail-context'
import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
export default function useReviewPanelLayout(): {
showPanel: boolean
showHeader: boolean
mini: boolean
} {
const ranges = useRangesContext()
const threads = useThreadsContext()
const { selectedTab: selectedRailTab, isOpen: railIsOpen } = useRailContext()
const { reviewPanelOpen: reviewPanelOpenOldEditor } = useLayoutContext()
const newEditor = useIsNewEditorEnabled()
const reviewPanelOpen = newEditor
? selectedRailTab === 'review-panel' && railIsOpen
: reviewPanelOpenOldEditor
const hasCommentOrChange = hasActiveRange(ranges, threads)
const showPanel = reviewPanelOpen || !!hasCommentOrChange
const mini = !reviewPanelOpen
const showHeader = showPanel && !mini
return { showPanel, showHeader, mini }
}

View File

@@ -0,0 +1,53 @@
import { CSSProperties, useCallback, useEffect, useState } from 'react'
import { useCodeMirrorViewContext } from '@/features/source-editor/components/codemirror-context'
import getMeta from '@/utils/meta'
export const useReviewPanelStyles = (mini: boolean) => {
const view = useCodeMirrorViewContext()
const [styles, setStyles] = useState<CSSProperties>({
'--review-panel-header-height': getMeta('ol-isReviewerRoleEnabled')
? '36px'
: '69px',
} as CSSProperties)
const updateScrollDomVariables = useCallback((element: HTMLDivElement) => {
const { top, bottom } = element.getBoundingClientRect()
setStyles(value => ({
...value,
'--review-panel-top': `${top}px`,
'--review-panel-bottom': `${bottom}px`,
}))
}, [])
const updateContentDomVariables = useCallback((element: HTMLDivElement) => {
const { height } = element.getBoundingClientRect()
setStyles(value => ({
...value,
'--review-panel-height': `${height}px`,
}))
}, [])
useEffect(() => {
if ('ResizeObserver' in window) {
const scrollDomObserver = new window.ResizeObserver(entries =>
updateScrollDomVariables(entries[0]?.target as HTMLDivElement)
)
scrollDomObserver.observe(view.scrollDOM)
const contentDomObserver = new window.ResizeObserver(entries =>
updateContentDomVariables(entries[0]?.target as HTMLDivElement)
)
contentDomObserver.observe(view.contentDOM)
return () => {
scrollDomObserver.disconnect()
contentDomObserver.disconnect()
}
}
}, [view, updateScrollDomVariables, updateContentDomVariables])
return styles
}

View File

@@ -0,0 +1,31 @@
import { useCallback, useState, Dispatch, SetStateAction } from 'react'
export default function useSubmittableTextInput(
handleSubmit: (
content: string,
setContent: Dispatch<SetStateAction<string>>
) => void
) {
const [content, setContent] = useState('')
const handleKeyPress = useCallback(
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
e.preventDefault()
if (content.trim().length > 0) {
handleSubmit(content, setContent)
}
}
},
[content, handleSubmit]
)
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
setContent(e.target.value)
},
[]
)
return { handleChange, handleKeyPress, content }
}