diff --git a/web/src/components/Changes/Changes.tsx b/web/src/components/Changes/Changes.tsx index 2cd56501a..be510854f 100644 --- a/web/src/components/Changes/Changes.tsx +++ b/web/src/components/Changes/Changes.tsx @@ -23,12 +23,7 @@ import { useEventListener } from 'hooks/useEventListener' import { UserPreference, useUserPreference } from 'hooks/useUserPreference' import { PipeSeparator } from 'components/PipeSeparator/PipeSeparator' import type { DiffFileEntry } from 'utils/types' -import { - DIFF2HTML_CONFIG, - PR_CODE_COMMENT_PAYLOAD_VERSION, - PullRequestCodeCommentPayload, - ViewStyle -} from 'components/DiffViewer/DiffViewerUtils' +import { DIFF2HTML_CONFIG, PullRequestCodeCommentPayload, ViewStyle } from 'components/DiffViewer/DiffViewerUtils' import { NoResultCard } from 'components/NoResultCard/NoResultCard' import type { TypesPullReq, TypesPullReqActivity } from 'services/code' import { useShowRequestError } from 'hooks/useShowRequestError' @@ -118,14 +113,10 @@ export const Changes: React.FC = ({ const fileId = changedFileId([diff.oldName, diff.newName]) const containerId = `container-${fileId}` const contentId = `content-${fileId}` - const fileTitle = diff.isDeleted - ? diff.oldName - : diff.isRename - ? `${diff.oldName} -> ${diff.newName}` - : diff.newName + const filePath = diff.isDeleted ? diff.oldName : diff.newName const fileActivities: TypesPullReqActivity[] | undefined = activities?.filter(activity => { const payload = activity.payload as PullRequestCodeCommentPayload - return payload?.file_id === fileId && payload?.version === PR_CODE_COMMENT_PAYLOAD_VERSION + return payload?.file_id === fileId }) return { @@ -133,7 +124,7 @@ export const Changes: React.FC = ({ containerId, contentId, fileId, - fileTitle, + filePath, fileActivities: fileActivities || [], activities: activities || [] } diff --git a/web/src/components/DiffViewer/DiffViewer.tsx b/web/src/components/DiffViewer/DiffViewer.tsx index 305930fca..ca69a0061 100644 --- a/web/src/components/DiffViewer/DiffViewer.tsx +++ b/web/src/components/DiffViewer/DiffViewer.tsx @@ -24,7 +24,7 @@ import type { DiffFileEntry } from 'utils/types' import { useConfirmAct } from 'hooks/useConfirmAction' import { PipeSeparator } from 'components/PipeSeparator/PipeSeparator' import { useAppContext } from 'AppContext' -import type { TypesPullReq, TypesPullReqActivity } from 'services/code' +import type { OpenapiCommentCreatePullReqRequest, TypesPullReq, TypesPullReqActivity } from 'services/code' import { getErrorMessage } from 'utils/Utils' import { CopyButton } from 'components/CopyButton/CopyButton' import { AppWrapper } from 'App' @@ -36,9 +36,6 @@ import { DiffCommentItem, DIFF_VIEWER_HEADER_HEIGHT, getCommentLineInfo, - getDiffHTMLSnapshotFromRow, - getRawTextInRange, - PR_CODE_COMMENT_PAYLOAD_VERSION, PullRequestCodeCommentPayload, renderCommentOppositePlaceHolder, ViewStyle @@ -308,22 +305,33 @@ export const DiffViewer: React.FC = ({ switch (action) { case CommentAction.NEW: { - // lineNumberRange can be used to allow multiple-line selection when commenting in the future - const lineNumberRange = [comment.lineNumber] - const payload: PullRequestCodeCommentPayload = { - type: CommentType.CODE_COMMENT, - version: PR_CODE_COMMENT_PAYLOAD_VERSION, - file_id: diff.fileId, - file_title: diff.fileTitle, - language: diff.language || '', - is_on_left: comment.left, - at_line_number: comment.lineNumber, - line_number_range: lineNumberRange, - range_text_content: getRawTextInRange(diff, lineNumberRange), - diff_html_snapshot: getDiffHTMLSnapshotFromRow(rowElement) + const commentPayload: OpenapiCommentCreatePullReqRequest = { + line_start: comment.lineNumber, + line_end: comment.lineNumber, + line_start_new: !comment.left, + line_end_new: !comment.left, + path: diff.filePath, + source_commit_sha: pullRequestMetadata?.merge_sha || '', + target_commit_sha: pullRequestMetadata?.merge_target_sha || '', + text: value } - await saveComment({ type: CommentType.CODE_COMMENT, text: value, payload }) + // lineNumberRange can be used to allow multiple-line selection when commenting in the future + // const lineNumberRange = [comment.lineNumber] + // const payload: PullRequestCodeCommentPayload = { + // type: CommentType.CODE_COMMENT, + // version: PR_CODE_COMMENT_PAYLOAD_VERSION, + // file_id: diff.fileId, + // file_title: diff.filePath, + // language: diff.language || '', + // is_on_left: comment.left, + // at_line_number: comment.lineNumber, + // line_number_range: lineNumberRange, + // range_text_content: getRawTextInRange(diff, lineNumberRange), + // diff_html_snapshot: getDiffHTMLSnapshotFromRow(rowElement) + // } + + await saveComment({ type: CommentType.CODE_COMMENT, ...commentPayload }) .then((newComment: TypesPullReqActivity) => { updatedItem = activityToCommentItem(newComment) }) @@ -472,12 +480,12 @@ export const DiffViewer: React.FC = ({ to={routes.toCODERepository({ repoPath: repoMetadata.path as string, gitRef: pullRequestMetadata?.source_branch, - resourcePath: diff.fileTitle + resourcePath: diff.isRename ? diff.newName : diff.filePath })}> - {diff.fileTitle} + {diff.isRename ? `${diff.oldName} -> ${diff.newName}` : diff.filePath} - + diff --git a/web/src/components/DiffViewer/DiffViewerUtils.tsx b/web/src/components/DiffViewer/DiffViewerUtils.tsx index 95dd4237c..e30127693 100644 --- a/web/src/components/DiffViewer/DiffViewerUtils.tsx +++ b/web/src/components/DiffViewer/DiffViewerUtils.tsx @@ -21,8 +21,6 @@ export enum CommentType { STATE_CHANGE = 'state-change' } -export const PR_CODE_COMMENT_PAYLOAD_VERSION = '0.1' - export interface PullRequestCodeCommentPayload { type: CommentType version: string // used to avoid rendering old payload structure @@ -200,78 +198,6 @@ export const activityToCommentItem = (activity: TypesPullReqActivity): CommentIt payload: activity }) -/** - * Take a small HTML snapshot of a diff in order to render code comment. - * @param atRow Row element where the comment is placed. - * @param maxNumberOfLines Maximum number of lines to take. - * @returns HTML content of the diff. - */ -export function getDiffHTMLSnapshotFromRow(atRow: HTMLTableRowElement, maxNumberOfLines = 5) { - let linesCapturedCount = 0 - const diffSnapshot = [atRow.outerHTML.trim()] - - let prev = atRow.previousElementSibling - - while (prev && linesCapturedCount < maxNumberOfLines) { - if (!prev.hasAttribute('data-annotated-line') && !prev.hasAttribute('data-place-holder-for-line')) { - // Don't count empty lines - const textContent = prev.textContent?.replace(/\s/g, '') - if (textContent?.length && textContent !== '+') { - linesCapturedCount++ - } - - if (textContent !== '+') { - diffSnapshot.unshift((prev.outerHTML || '').trim()) - } - } - prev = prev.previousElementSibling - } - - return diffSnapshot.join('') -} - -// export function getDiffHTMLSnapshotFromDiff(diff: DiffFileEntry, lineNumberRange: number[], isOnLeft: boolean) { -// const lines = diff?.blocks.reduce((group, item) => { -// group = group.concat(item.lines) -// return group -// }, [] as DiffLine[]) - -// const lastIndex = lines.findIndex(line => -// lineNumberRange.includes((isOnLeft ? line.oldNumber : line.newNumber) as number) -// ) -// const startIndex = Math.max(0, lastIndex - 5) - -// const copiedLines = lines.slice(startIndex, lastIndex) -// const copiedDiff: DiffFileEntry = { -// ...diff, -// blocks: [ -// { -// header: '', -// lines: copiedLines, -// newStartLine: copiedLines[0].newNumber as number, -// oldStartLine: copiedLines[0].oldNumber as number -// } -// ] -// } - -// // console.log({ isOnLeft, startIndex, lastIndex, copiedLines, lines, lineNumberRange }) - -// const div = document.createElement('div') -// new Diff2HtmlUI(div, [copiedDiff], Object.assign({}, DIFF2HTML_CONFIG, { outputFormat: 'line-by-line' })).draw() - -// return div.querySelector('table')?.outerHTML || '' -// } - -export function getRawTextInRange(diff: DiffFileEntry, lineNumberRange: number[]) { - return ( - // TODO: This is wrong, blocks can have multiple items, not one - (diff?.blocks[0]?.lines || []) - .filter(line => lineNumberRange.includes(line.newNumber as number)) - .map(line => line.content) - .join('\n') || '' - ) -} - export function activitiesToDiffCommentItems(diff: DiffFileEntry): DiffCommentItem[] { return ( diff.fileActivities?.map(activity => { diff --git a/web/src/components/OptionsMenuButton/OptionsMenuButton.tsx b/web/src/components/OptionsMenuButton/OptionsMenuButton.tsx index 99dc29888..3d13ff28d 100644 --- a/web/src/components/OptionsMenuButton/OptionsMenuButton.tsx +++ b/web/src/components/OptionsMenuButton/OptionsMenuButton.tsx @@ -63,7 +63,8 @@ export const OptionsMenuButton = ({ item as IMenuItemProps & React.AnchorHTMLAttributes, 'isDanger', 'hasIcon', - 'iconName' + 'iconName', + 'iconSize' )} /> ) diff --git a/web/src/components/SourceCodeEditor/MonacoSourceCodeEditor.tsx b/web/src/components/SourceCodeEditor/MonacoSourceCodeEditor.tsx index 9abc286c3..46ac697ce 100644 --- a/web/src/components/SourceCodeEditor/MonacoSourceCodeEditor.tsx +++ b/web/src/components/SourceCodeEditor/MonacoSourceCodeEditor.tsx @@ -1,9 +1,9 @@ -import React, { useEffect } from 'react' -import { Container } from '@harness/uicore' +import React, { useEffect, useState } from 'react' import type monacoEditor from 'monaco-editor/esm/vs/editor/editor.api' -import MonacoEditor from 'react-monaco-editor' +import MonacoEditor, { MonacoDiffEditor } from 'react-monaco-editor' import { noop } from 'lodash-es' import { SourceCodeEditorProps, PLAIN_TEXT } from 'utils/Utils' +import { useEventListener } from 'hooks/useEventListener' export const MonacoEditorOptions = { ignoreTrimWhitespace: true, @@ -58,12 +58,12 @@ export default function MonacoSourceCodeEditor({ language = PLAIN_TEXT, lineNumbers = true, readOnly = false, - className, height, autoHeight, wordWrap = true, onChange = noop }: SourceCodeEditorProps) { + const [editor, setEditor] = useState() const scrollbar = autoHeight ? 'hidden' : 'auto' useEffect(() => { @@ -72,31 +72,80 @@ export default function MonacoSourceCodeEditor({ monaco.languages.typescript?.typescriptDefaults?.setCompilerOptions?.(compilerOptions) }, []) + useEventListener('resize', () => { + editor?.layout({ width: 0, height: 0 }) + window.requestAnimationFrame(() => editor?.layout()) + }) + return ( - - { - if (autoHeight) { - autoAdjustEditorHeight(_editor) - } - }} - onChange={onChange} - /> - + { + if (autoHeight) { + autoAdjustEditorHeight(_editor) + } + setEditor(_editor) + }} + onChange={onChange} + /> + ) +} + +interface DiffEditorProps extends Omit { + original: string +} + +export function DiffEditor({ + source, + original, + language = PLAIN_TEXT, + lineNumbers = true, + readOnly = false, + height, + wordWrap = true, + onChange = noop +}: DiffEditorProps) { + const [editor, setEditor] = useState() + + useEventListener('resize', () => { + editor?.layout({ width: 0, height: 0 }) + window.requestAnimationFrame(() => editor?.layout()) + }) + + return ( + ) } diff --git a/web/src/framework/strings/stringTypes.ts b/web/src/framework/strings/stringTypes.ts index 9728fee7e..96eacc100 100644 --- a/web/src/framework/strings/stringTypes.ts +++ b/web/src/framework/strings/stringTypes.ts @@ -38,6 +38,7 @@ export interface StringsMap { branches: string browse: string cancel: string + changes: string checkRuns: string checkSuites: string checks: string @@ -62,6 +63,7 @@ export interface StringsMap { confirmDeleteWebhook: string confirmation: string content: string + contents: string conversation: string copy: string copyCommitSHA: string diff --git a/web/src/i18n/strings.en.yaml b/web/src/i18n/strings.en.yaml index 2077c65ff..3aad3b792 100644 --- a/web/src/i18n/strings.en.yaml +++ b/web/src/i18n/strings.en.yaml @@ -192,7 +192,7 @@ pr: failedToDeleteComment: Failed to delete comment. Please try again. prMerged: This Pull Request was merged reviewSubmitted: Review submitted. - prReviewSubmit: '{user} {state|approved:approved, rejected:rejected,changereq:requested changes to, reviewed} the pull request. {time}' + prReviewSubmit: '{user} {state|approved:approved, rejected:rejected,changereq:requested to, reviewed} the pull request. {time}' prMergedInfo: '{user} merged branch {source} into {target} {time}.' prBranchPushInfo: '{user} pushed a new commit {commit}.' prStateChanged: '{user} changed pull request state from {old} to {new}.' @@ -375,3 +375,5 @@ manageCredText: You can also manage your git credential {URL} blame: Blame viewRaw: View Raw download: Download +changes: Changes +contents: Contents diff --git a/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.module.scss b/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.module.scss index 360f13a08..0deab39b9 100644 --- a/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.module.scss +++ b/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.module.scss @@ -1,9 +1,13 @@ .container { + --header-height: 96px; + --heading-height: 58px; + --tabs-height: 36px; + background-color: var(--white) !important; margin-top: 0 !important; // box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16); // border-radius: 4px; - height: calc(100vh - 96px); + height: calc(100vh - var(--header-height)); overflow: hidden; .heading { @@ -11,7 +15,7 @@ // border-top-right-radius: 4px; align-items: center; padding: 0 var(--spacing-xlarge) !important; - height: 58px; + height: var(--heading-height); background-color: var(--grey-100); box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16); border-bottom: 1px solid var(--grey-200); @@ -40,13 +44,42 @@ } } - .content { + .tabs { + height: var(--tabs-height); + display: flex; + align-items: center; + justify-content: center; + background-color: var(--grey-50); + --tab-height: 18px; + + .selectedView { + height: var(--tab-height); + + > div { + align-items: center; + display: flex; + height: var(--tab-height) !important; + width: auto; + padding: 0 var(--spacing-large); + font-weight: 700; + font-size: 10px; + text-transform: uppercase; + + &[class*='selected'] { + background-color: var(--primary-9); + } + + &:not([class*='selected']) { + color: var(--primary-9); + } + } + } + } + + .editorContainer { padding-left: var(--spacing-medium) !important; overflow: hidden; - .editorContainer { - height: calc(100vh - 96px - 58px); - overflow: hidden; - } + height: calc(100vh - var(--header-height) - var(--heading-height) - var(--tabs-height)); } } diff --git a/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.module.scss.d.ts b/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.module.scss.d.ts index 2e3e51811..2853f09db 100644 --- a/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.module.scss.d.ts +++ b/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.module.scss.d.ts @@ -6,7 +6,8 @@ declare const styles: { readonly path: string readonly inputContainer: string readonly refLink: string - readonly content: string + readonly tabs: string + readonly selectedView: string readonly editorContainer: string } export default styles diff --git a/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.tsx b/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.tsx index 9e9aa3d95..e6a2553dc 100644 --- a/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.tsx +++ b/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.tsx @@ -1,5 +1,17 @@ import React, { ChangeEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { Button, ButtonVariation, Color, Container, FlexExpander, Icon, Layout, Text, TextInput } from '@harness/uicore' +import { + Button, + ButtonVariation, + Color, + Container, + FlexExpander, + Icon, + Layout, + Text, + TextInput, + VisualYamlSelectedView, + VisualYamlToggle +} from '@harness/uicore' import { Link, useHistory } from 'react-router-dom' import ReactJoin from 'react-join' import cx from 'classnames' @@ -11,6 +23,7 @@ import { useStrings } from 'framework/strings' import { filenameToLanguage, FILE_SEPERATOR } from 'utils/Utils' import { useGetResourceContent } from 'hooks/useGetResourceContent' import { CommitModalButton } from 'components/CommitModalButton/CommitModalButton' +import { DiffEditor } from 'components/SourceCodeEditor/MonacoSourceCodeEditor' import css from './FileEditor.module.scss' interface EditorProps extends Pick { @@ -83,6 +96,7 @@ function Editor({ resourceContent, repoMetadata, gitRef, resourcePath, isReposit // Make API call to verify if fileResourcePath is an existing folder verifyFolder().then(() => setStartVerifyFolder(true)) }, [fileName, parentPath, language, content, verifyFolder]) + const [selectedView, setSelectedView] = useState(VisualYamlSelectedView.VISUAL) // Calculate file name input field width based on number of characters inside useEffect(() => { @@ -111,16 +125,14 @@ function Editor({ resourceContent, repoMetadata, gitRef, resourcePath, isReposit } } }, [isNew, name]) + return ( - - {/* - {repoMetadata.uid} - */} + {parentPath && ( @@ -148,7 +160,7 @@ function Editor({ resourceContent, repoMetadata, gitRef, resourcePath, isReposit (inputRef.current = ref)} + inputRef={_ref => (inputRef.current = _ref)} wrapperClassName={css.inputContainer} placeholder={getString('nameYourFile')} onInput={(event: ChangeEvent) => { @@ -231,15 +243,22 @@ function Editor({ resourceContent, repoMetadata, gitRef, resourcePath, isReposit - - + + + + {selectedView === VisualYamlSelectedView.VISUAL ? ( + + ) : ( + + )} + ) } diff --git a/web/src/utils/Utils.ts b/web/src/utils/Utils.ts index dfd3362f3..a014289e5 100644 --- a/web/src/utils/Utils.ts +++ b/web/src/utils/Utils.ts @@ -49,7 +49,6 @@ export interface SourceCodeEditorProps { lineNumbers?: boolean readOnly?: boolean highlightLines?: string // i.e: {1,3-4}, TODO: not yet supported - className?: string height?: number | string autoHeight?: boolean wordWrap?: boolean diff --git a/web/src/utils/types.ts b/web/src/utils/types.ts index 6080b466b..233ef0f2b 100644 --- a/web/src/utils/types.ts +++ b/web/src/utils/types.ts @@ -3,7 +3,7 @@ import type { TypesPullReqActivity } from 'services/code' export interface DiffFileEntry extends DiffFile { fileId: string - fileTitle: string + filePath: string containerId: string contentId: string fileActivities?: TypesPullReqActivity[]