diff options
| author | David Hill <[email protected]> | 2026-03-02 16:19:35 +0000 |
|---|---|---|
| committer | David Hill <[email protected]> | 2026-03-02 16:19:35 +0000 |
| commit | 0a3a3216db5974efd3edc9a213054fd97d8dbd34 (patch) | |
| tree | 8bc0554797fae57c0ffeaff6d27d34ffdb501433 /packages | |
| parent | 633a3ba03adf9983e361efe994f34d405b573cbc (diff) | |
| download | opencode-0a3a3216db5974efd3edc9a213054fd97d8dbd34.tar.gz opencode-0a3a3216db5974efd3edc9a213054fd97d8dbd34.zip | |
ui: move session review bottom padding
Remove bottom padding from the scroll wrapper and apply it to the accordion content instead.
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/app/src/pages/session/review-tab.tsx | 2 | ||||
| -rw-r--r-- | packages/ui/src/components/session-review.tsx | 528 |
2 files changed, 266 insertions, 264 deletions
diff --git a/packages/app/src/pages/session/review-tab.tsx b/packages/app/src/pages/session/review-tab.tsx index 1b285407b..2b7eba324 100644 --- a/packages/app/src/pages/session/review-tab.tsx +++ b/packages/app/src/pages/session/review-tab.tsx @@ -176,7 +176,7 @@ export function SessionReviewTab(props: SessionReviewTabProps) { open={props.view().review.open()} onOpenChange={props.view().review.setOpen} classes={{ - root: props.classes?.root ?? "pb-6 pr-3", + root: props.classes?.root ?? "pr-3", header: props.classes?.header ?? "px-3", container: props.classes?.container ?? "pl-3", }} diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index 2f74207d5..c75baf921 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -621,279 +621,281 @@ export const SessionReview = (props: SessionReviewProps) => { <div data-slot="session-review-container" class={props.classes?.container}> <Show when={hasDiffs()} fallback={props.empty}> - <Accordion multiple value={open()} onChange={handleChange}> - <For each={files()}> - {(file) => { - let wrapper: HTMLDivElement | undefined - - const diff = createMemo(() => diffs().get(file)) - const item = () => diff()! - - const expanded = createMemo(() => open().includes(file)) - const force = () => !!store.force[file] - - const comments = createMemo(() => (props.comments ?? []).filter((c) => c.file === file)) - const commentedLines = createMemo(() => comments().map((c) => c.selection)) - - const beforeText = () => (typeof item().before === "string" ? item().before : "") - const afterText = () => (typeof item().after === "string" ? item().after : "") - const changedLines = () => item().additions + item().deletions - const mediaKind = createMemo(() => mediaKindFromPath(file)) - - const tooLarge = createMemo(() => { - if (!expanded()) return false - if (force()) return false - if (mediaKind()) return false - return changedLines() > MAX_DIFF_CHANGED_LINES - }) - - const isAdded = () => - item().status === "added" || (beforeText().length === 0 && afterText().length > 0) - const isDeleted = () => - item().status === "deleted" || (afterText().length === 0 && beforeText().length > 0) - - const selectedLines = createMemo(() => { - const current = selection() - if (!current || current.file !== file) return null - return current.range - }) - - const draftRange = createMemo(() => { - const current = commenting() - if (!current || current.file !== file) return null - return current.range - }) - - const commentsUi = createLineCommentController<SessionReviewComment>({ - comments, - label: i18n.t("ui.lineComment.submit"), - draftKey: () => file, - state: { - opened: () => { - const current = opened() - if (!current || current.file !== file) return null - return current.id + <div class="pb-6"> + <Accordion multiple value={open()} onChange={handleChange}> + <For each={files()}> + {(file) => { + let wrapper: HTMLDivElement | undefined + + const diff = createMemo(() => diffs().get(file)) + const item = () => diff()! + + const expanded = createMemo(() => open().includes(file)) + const force = () => !!store.force[file] + + const comments = createMemo(() => (props.comments ?? []).filter((c) => c.file === file)) + const commentedLines = createMemo(() => comments().map((c) => c.selection)) + + const beforeText = () => (typeof item().before === "string" ? item().before : "") + const afterText = () => (typeof item().after === "string" ? item().after : "") + const changedLines = () => item().additions + item().deletions + const mediaKind = createMemo(() => mediaKindFromPath(file)) + + const tooLarge = createMemo(() => { + if (!expanded()) return false + if (force()) return false + if (mediaKind()) return false + return changedLines() > MAX_DIFF_CHANGED_LINES + }) + + const isAdded = () => + item().status === "added" || (beforeText().length === 0 && afterText().length > 0) + const isDeleted = () => + item().status === "deleted" || (afterText().length === 0 && beforeText().length > 0) + + const selectedLines = createMemo(() => { + const current = selection() + if (!current || current.file !== file) return null + return current.range + }) + + const draftRange = createMemo(() => { + const current = commenting() + if (!current || current.file !== file) return null + return current.range + }) + + const commentsUi = createLineCommentController<SessionReviewComment>({ + comments, + label: i18n.t("ui.lineComment.submit"), + draftKey: () => file, + state: { + opened: () => { + const current = opened() + if (!current || current.file !== file) return null + return current.id + }, + setOpened: (id) => setOpened(id ? { file, id } : null), + selected: selectedLines, + setSelected: (range) => setSelection(range ? { file, range } : null), + commenting: draftRange, + setCommenting: (range) => setCommenting(range ? { file, range } : null), }, - setOpened: (id) => setOpened(id ? { file, id } : null), - selected: selectedLines, - setSelected: (range) => setSelection(range ? { file, range } : null), - commenting: draftRange, - setCommenting: (range) => setCommenting(range ? { file, range } : null), - }, - getSide: selectionSide, - clearSelectionOnSelectionEndNull: false, - onSubmit: ({ comment, selection }) => { - props.onLineComment?.({ - file, - selection, - comment, - preview: selectionPreview(item(), selection), - }) - }, - onUpdate: ({ id, comment, selection }) => { - props.onLineCommentUpdate?.({ - id, - file, - selection, - comment, - preview: selectionPreview(item(), selection), - }) - }, - onDelete: (comment) => { - props.onLineCommentDelete?.({ - id: comment.id, - file, - }) - }, - editSubmitLabel: props.lineCommentActions?.saveLabel, - renderCommentActions: props.lineCommentActions - ? (comment, controls) => ( - <ReviewCommentMenu - labels={props.lineCommentActions!} - onEdit={controls.edit} - onDelete={controls.remove} - /> - ) - : undefined, - }) - - onCleanup(() => { - anchors.delete(file) - readyFiles.delete(file) - searchHandles.delete(file) - if (highlightedFile === file) highlightedFile = undefined - }) - - const handleLineSelected = (range: SelectedLineRange | null) => { - if (!props.onLineComment) return - commentsUi.onLineSelected(range) - } - - const handleLineSelectionEnd = (range: SelectedLineRange | null) => { - if (!props.onLineComment) return - commentsUi.onLineSelectionEnd(range) - } - - return ( - <Accordion.Item - value={file} - id={diffId(file)} - data-file={file} - data-slot="session-review-accordion-item" - data-selected={props.focusedFile === file ? "" : undefined} - > - <StickyAccordionHeader> - <Accordion.Trigger> - <div data-slot="session-review-trigger-content"> - <div data-slot="session-review-file-info"> - <FileIcon node={{ path: file, type: "file" }} /> - <div data-slot="session-review-file-name-container"> - <Show when={file.includes("/")}> - <span data-slot="session-review-directory">{`\u202A${getDirectory(file)}\u202C`}</span> - </Show> - <span data-slot="session-review-filename">{getFilename(file)}</span> - <Show when={props.onViewFile}> - <Tooltip value={openFileLabel()} placement="top" gutter={4}> - <button - data-slot="session-review-view-button" - type="button" - aria-label={openFileLabel()} - onClick={(e) => { - e.stopPropagation() - props.onViewFile?.(file) - }} - > - <Icon name="open-file" size="small" /> - </button> - </Tooltip> - </Show> + getSide: selectionSide, + clearSelectionOnSelectionEndNull: false, + onSubmit: ({ comment, selection }) => { + props.onLineComment?.({ + file, + selection, + comment, + preview: selectionPreview(item(), selection), + }) + }, + onUpdate: ({ id, comment, selection }) => { + props.onLineCommentUpdate?.({ + id, + file, + selection, + comment, + preview: selectionPreview(item(), selection), + }) + }, + onDelete: (comment) => { + props.onLineCommentDelete?.({ + id: comment.id, + file, + }) + }, + editSubmitLabel: props.lineCommentActions?.saveLabel, + renderCommentActions: props.lineCommentActions + ? (comment, controls) => ( + <ReviewCommentMenu + labels={props.lineCommentActions!} + onEdit={controls.edit} + onDelete={controls.remove} + /> + ) + : undefined, + }) + + onCleanup(() => { + anchors.delete(file) + readyFiles.delete(file) + searchHandles.delete(file) + if (highlightedFile === file) highlightedFile = undefined + }) + + const handleLineSelected = (range: SelectedLineRange | null) => { + if (!props.onLineComment) return + commentsUi.onLineSelected(range) + } + + const handleLineSelectionEnd = (range: SelectedLineRange | null) => { + if (!props.onLineComment) return + commentsUi.onLineSelectionEnd(range) + } + + return ( + <Accordion.Item + value={file} + id={diffId(file)} + data-file={file} + data-slot="session-review-accordion-item" + data-selected={props.focusedFile === file ? "" : undefined} + > + <StickyAccordionHeader> + <Accordion.Trigger> + <div data-slot="session-review-trigger-content"> + <div data-slot="session-review-file-info"> + <FileIcon node={{ path: file, type: "file" }} /> + <div data-slot="session-review-file-name-container"> + <Show when={file.includes("/")}> + <span data-slot="session-review-directory">{`\u202A${getDirectory(file)}\u202C`}</span> + </Show> + <span data-slot="session-review-filename">{getFilename(file)}</span> + <Show when={props.onViewFile}> + <Tooltip value={openFileLabel()} placement="top" gutter={4}> + <button + data-slot="session-review-view-button" + type="button" + aria-label={openFileLabel()} + onClick={(e) => { + e.stopPropagation() + props.onViewFile?.(file) + }} + > + <Icon name="open-file" size="small" /> + </button> + </Tooltip> + </Show> + </div> </div> - </div> - <div data-slot="session-review-trigger-actions"> - <Switch> - <Match when={isAdded()}> - <div data-slot="session-review-change-group" data-type="added"> - <span data-slot="session-review-change" data-type="added"> - {i18n.t("ui.sessionReview.change.added")} + <div data-slot="session-review-trigger-actions"> + <Switch> + <Match when={isAdded()}> + <div data-slot="session-review-change-group" data-type="added"> + <span data-slot="session-review-change" data-type="added"> + {i18n.t("ui.sessionReview.change.added")} + </span> + <DiffChanges changes={item()} /> + </div> + </Match> + <Match when={isDeleted()}> + <span data-slot="session-review-change" data-type="removed"> + {i18n.t("ui.sessionReview.change.removed")} + </span> + </Match> + <Match when={!!mediaKind()}> + <span data-slot="session-review-change" data-type="modified"> + {i18n.t("ui.sessionReview.change.modified")} </span> + </Match> + <Match when={true}> <DiffChanges changes={item()} /> + </Match> + </Switch> + <span data-slot="session-review-diff-chevron"> + <Icon name="chevron-down" size="small" /> + </span> + </div> + </div> + </Accordion.Trigger> + </StickyAccordionHeader> + <Accordion.Content data-slot="session-review-accordion-content"> + <div + data-slot="session-review-diff-wrapper" + ref={(el) => { + wrapper = el + anchors.set(file, el) + }} + > + <Show when={expanded()}> + <Switch> + <Match when={tooLarge()}> + <div data-slot="session-review-large-diff"> + <div data-slot="session-review-large-diff-title"> + {i18n.t("ui.sessionReview.largeDiff.title")} + </div> + <div data-slot="session-review-large-diff-meta"> + {i18n.t("ui.sessionReview.largeDiff.meta", { + limit: MAX_DIFF_CHANGED_LINES.toLocaleString(), + current: changedLines().toLocaleString(), + })} + </div> + <div data-slot="session-review-large-diff-actions"> + <Button + size="normal" + variant="secondary" + onClick={() => setStore("force", file, true)} + > + {i18n.t("ui.sessionReview.largeDiff.renderAnyway")} + </Button> + </div> </div> </Match> - <Match when={isDeleted()}> - <span data-slot="session-review-change" data-type="removed"> - {i18n.t("ui.sessionReview.change.removed")} - </span> - </Match> - <Match when={!!mediaKind()}> - <span data-slot="session-review-change" data-type="modified"> - {i18n.t("ui.sessionReview.change.modified")} - </span> - </Match> <Match when={true}> - <DiffChanges changes={item()} /> + <Dynamic + component={fileComponent} + mode="diff" + preloadedDiff={item().preloaded} + diffStyle={diffStyle()} + expansionLineCount={searchExpanded() ? Number.MAX_SAFE_INTEGER : 20} + onRendered={() => { + readyFiles.add(file) + props.onDiffRendered?.() + }} + enableLineSelection={props.onLineComment != null} + enableHoverUtility={props.onLineComment != null} + onLineSelected={handleLineSelected} + onLineSelectionEnd={handleLineSelectionEnd} + onLineNumberSelectionEnd={commentsUi.onLineNumberSelectionEnd} + annotations={commentsUi.annotations()} + renderAnnotation={commentsUi.renderAnnotation} + renderHoverUtility={props.onLineComment ? commentsUi.renderHoverUtility : undefined} + selectedLines={selectedLines()} + commentedLines={commentedLines()} + search={{ + shortcuts: "disabled", + showBar: false, + disableVirtualization: searchExpanded(), + register: (handle: FileSearchHandle | null) => { + if (!handle) { + searchHandles.delete(file) + readyFiles.delete(file) + if (highlightedFile === file) highlightedFile = undefined + return + } + + searchHandles.set(file, handle) + }, + }} + before={{ + name: file, + contents: typeof item().before === "string" ? item().before : "", + }} + after={{ + name: file, + contents: typeof item().after === "string" ? item().after : "", + }} + media={{ + mode: "auto", + path: file, + before: item().before, + after: item().after, + readFile: props.readFile, + }} + /> </Match> </Switch> - <span data-slot="session-review-diff-chevron"> - <Icon name="chevron-down" size="small" /> - </span> - </div> + </Show> </div> - </Accordion.Trigger> - </StickyAccordionHeader> - <Accordion.Content data-slot="session-review-accordion-content"> - <div - data-slot="session-review-diff-wrapper" - ref={(el) => { - wrapper = el - anchors.set(file, el) - }} - > - <Show when={expanded()}> - <Switch> - <Match when={tooLarge()}> - <div data-slot="session-review-large-diff"> - <div data-slot="session-review-large-diff-title"> - {i18n.t("ui.sessionReview.largeDiff.title")} - </div> - <div data-slot="session-review-large-diff-meta"> - {i18n.t("ui.sessionReview.largeDiff.meta", { - limit: MAX_DIFF_CHANGED_LINES.toLocaleString(), - current: changedLines().toLocaleString(), - })} - </div> - <div data-slot="session-review-large-diff-actions"> - <Button - size="normal" - variant="secondary" - onClick={() => setStore("force", file, true)} - > - {i18n.t("ui.sessionReview.largeDiff.renderAnyway")} - </Button> - </div> - </div> - </Match> - <Match when={true}> - <Dynamic - component={fileComponent} - mode="diff" - preloadedDiff={item().preloaded} - diffStyle={diffStyle()} - expansionLineCount={searchExpanded() ? Number.MAX_SAFE_INTEGER : 20} - onRendered={() => { - readyFiles.add(file) - props.onDiffRendered?.() - }} - enableLineSelection={props.onLineComment != null} - enableHoverUtility={props.onLineComment != null} - onLineSelected={handleLineSelected} - onLineSelectionEnd={handleLineSelectionEnd} - onLineNumberSelectionEnd={commentsUi.onLineNumberSelectionEnd} - annotations={commentsUi.annotations()} - renderAnnotation={commentsUi.renderAnnotation} - renderHoverUtility={props.onLineComment ? commentsUi.renderHoverUtility : undefined} - selectedLines={selectedLines()} - commentedLines={commentedLines()} - search={{ - shortcuts: "disabled", - showBar: false, - disableVirtualization: searchExpanded(), - register: (handle: FileSearchHandle | null) => { - if (!handle) { - searchHandles.delete(file) - readyFiles.delete(file) - if (highlightedFile === file) highlightedFile = undefined - return - } - - searchHandles.set(file, handle) - }, - }} - before={{ - name: file, - contents: typeof item().before === "string" ? item().before : "", - }} - after={{ - name: file, - contents: typeof item().after === "string" ? item().after : "", - }} - media={{ - mode: "auto", - path: file, - before: item().before, - after: item().after, - readFile: props.readFile, - }} - /> - </Match> - </Switch> - </Show> - </div> - </Accordion.Content> - </Accordion.Item> - ) - }} - </For> - </Accordion> + </Accordion.Content> + </Accordion.Item> + ) + }} + </For> + </Accordion> + </div> </Show> </div> </ScrollView> |
