Buckets:
| import { createEffect, onCleanup, type JSX } from "solid-js" | |
| import { makeEventListener } from "@solid-primitives/event-listener" | |
| import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2" | |
| import { SessionReview } from "@opencode-ai/ui/session-review" | |
| import type { | |
| SessionReviewCommentActions, | |
| SessionReviewCommentDelete, | |
| SessionReviewCommentUpdate, | |
| } from "@opencode-ai/ui/session-review" | |
| import type { SelectedLineRange } from "@/context/file" | |
| import { useSDK } from "@/context/sdk" | |
| import { useLayout } from "@/context/layout" | |
| import type { LineComment } from "@/context/comments" | |
| export type DiffStyle = "unified" | "split" | |
| type ReviewDiff = SnapshotFileDiff | VcsFileDiff | |
| export interface SessionReviewTabProps { | |
| title?: JSX.Element | |
| empty?: JSX.Element | |
| diffs: () => ReviewDiff[] | |
| view: () => ReturnType<ReturnType<typeof useLayout>["view"]> | |
| diffStyle: DiffStyle | |
| onDiffStyleChange?: (style: DiffStyle) => void | |
| onViewFile?: (file: string) => void | |
| onLineComment?: (comment: { file: string; selection: SelectedLineRange; comment: string; preview?: string }) => void | |
| onLineCommentUpdate?: (comment: SessionReviewCommentUpdate) => void | |
| onLineCommentDelete?: (comment: SessionReviewCommentDelete) => void | |
| lineCommentActions?: SessionReviewCommentActions | |
| comments?: LineComment[] | |
| focusedComment?: { file: string; id: string } | null | |
| onFocusedCommentChange?: (focus: { file: string; id: string } | null) => void | |
| focusedFile?: string | |
| onScrollRef?: (el: HTMLDivElement | undefined) => void | |
| commentMentions?: { | |
| items: (query: string) => string[] | Promise<string[]> | |
| } | |
| classes?: { | |
| root?: string | |
| header?: string | |
| container?: string | |
| } | |
| } | |
| export function SessionReviewTab(props: SessionReviewTabProps) { | |
| let scroll: HTMLDivElement | undefined | |
| let restoreFrame: number | undefined | |
| let userInteracted = false | |
| let restored: { x: number; y: number } | undefined | |
| const sdk = useSDK() | |
| const layout = useLayout() | |
| const readFile = async (path: string) => { | |
| return sdk.client.file | |
| .read({ path }) | |
| .then((x) => x.data) | |
| .catch((error) => { | |
| console.debug("[session-review] failed to read file", { path, error }) | |
| return undefined | |
| }) | |
| } | |
| const handleInteraction = () => { | |
| userInteracted = true | |
| if (restoreFrame !== undefined) { | |
| cancelAnimationFrame(restoreFrame) | |
| restoreFrame = undefined | |
| } | |
| } | |
| const doRestore = () => { | |
| restoreFrame = undefined | |
| const el = scroll | |
| if (!el || !layout.ready() || userInteracted) return | |
| if (el.clientHeight === 0 || el.clientWidth === 0) return | |
| const s = props.view().scroll("review") | |
| if (!s || (s.x === 0 && s.y === 0)) return | |
| const maxY = Math.max(0, el.scrollHeight - el.clientHeight) | |
| const maxX = Math.max(0, el.scrollWidth - el.clientWidth) | |
| const targetY = Math.min(s.y, maxY) | |
| const targetX = Math.min(s.x, maxX) | |
| if (el.scrollTop === targetY && el.scrollLeft === targetX) return | |
| if (el.scrollTop !== targetY) el.scrollTop = targetY | |
| if (el.scrollLeft !== targetX) el.scrollLeft = targetX | |
| restored = { x: el.scrollLeft, y: el.scrollTop } | |
| } | |
| const queueRestore = () => { | |
| if (userInteracted || restoreFrame !== undefined) return | |
| restoreFrame = requestAnimationFrame(doRestore) | |
| } | |
| const handleScroll = (event: Event & { currentTarget: HTMLDivElement }) => { | |
| const el = event.currentTarget | |
| const prev = restored | |
| if (prev && el.scrollTop === prev.y && el.scrollLeft === prev.x) { | |
| restored = undefined | |
| return | |
| } | |
| restored = undefined | |
| handleInteraction() | |
| if (!layout.ready()) return | |
| if (el.clientHeight === 0 || el.clientWidth === 0) return | |
| props.view().setScroll("review", { | |
| x: el.scrollLeft, | |
| y: el.scrollTop, | |
| }) | |
| } | |
| createEffect(() => { | |
| props.diffs().length | |
| props.diffStyle | |
| if (!layout.ready()) return | |
| queueRestore() | |
| }) | |
| onCleanup(() => { | |
| if (restoreFrame !== undefined) cancelAnimationFrame(restoreFrame) | |
| props.onScrollRef?.(undefined) | |
| }) | |
| return ( | |
| <SessionReview | |
| title={props.title} | |
| empty={props.empty} | |
| scrollRef={(el) => { | |
| scroll = el | |
| makeEventListener(el, "wheel", handleInteraction, { passive: true, capture: true }) | |
| makeEventListener(el, "mousewheel", handleInteraction, { passive: true, capture: true }) | |
| makeEventListener(el, "pointerdown", handleInteraction, { passive: true, capture: true }) | |
| makeEventListener(el, "touchstart", handleInteraction, { passive: true, capture: true }) | |
| makeEventListener(el, "keydown", handleInteraction, { capture: true }) | |
| props.onScrollRef?.(el) | |
| queueRestore() | |
| }} | |
| onScroll={handleScroll} | |
| onDiffRendered={queueRestore} | |
| open={props.view().review.open()} | |
| onOpenChange={props.view().review.setOpen} | |
| classes={{ | |
| root: props.classes?.root ?? "pr-3", | |
| header: props.classes?.header ?? "px-3", | |
| container: props.classes?.container ?? "pl-3", | |
| }} | |
| diffs={props.diffs()} | |
| diffStyle={props.diffStyle} | |
| onDiffStyleChange={props.onDiffStyleChange} | |
| onViewFile={props.onViewFile} | |
| focusedFile={props.focusedFile} | |
| readFile={readFile} | |
| onLineComment={props.onLineComment} | |
| onLineCommentUpdate={props.onLineCommentUpdate} | |
| onLineCommentDelete={props.onLineCommentDelete} | |
| lineCommentActions={props.lineCommentActions} | |
| lineCommentMention={props.commentMentions} | |
| comments={props.comments} | |
| focusedComment={props.focusedComment} | |
| onFocusedCommentChange={props.onFocusedCommentChange} | |
| /> | |
| ) | |
| } | |
Xet Storage Details
- Size:
- 5.68 kB
- Xet hash:
- 211079b83b270f7fd991cf0dfe6d8d9d3e4b63093f9767e4d789b6691c48c1e3
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.