mirror of
https://github.com/harness/drone.git
synced 2025-05-20 02:50:05 +08:00
Merge branch 'ui/editor-dirty-check' of _OKE5H2PQKOUfzFFDuD4FA/default/CODE/gitness (#48)
This commit is contained in:
commit
db0fcbcde4
@ -1,124 +1,127 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { Radio, RadioGroup, ButtonVariation, Button, Container, Layout, ButtonSize, useToaster } from '@harness/uicore'
|
||||
import { Render } from 'react-jsx-match'
|
||||
import { useMutate } from 'restful-react'
|
||||
import cx from 'classnames'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import type { GitInfoProps } from 'utils/GitUtils'
|
||||
import type { TypesPullReq } from 'services/code'
|
||||
import { getErrorMessage } from 'utils/Utils'
|
||||
import { MarkdownEditorWithPreview } from 'components/MarkdownEditorWithPreview/MarkdownEditorWithPreview'
|
||||
import css from './ReviewDecisionButton.module.scss'
|
||||
// import React, { useCallback, useEffect, useState } from 'react'
|
||||
// import { Radio, RadioGroup, ButtonVariation, Button, Container, Layout, ButtonSize, useToaster } from '@harness/uicore'
|
||||
// import { Render } from 'react-jsx-match'
|
||||
// import { useMutate } from 'restful-react'
|
||||
// import cx from 'classnames'
|
||||
// import { useStrings } from 'framework/strings'
|
||||
// import type { GitInfoProps } from 'utils/GitUtils'
|
||||
// import type { TypesPullReq } from 'services/code'
|
||||
// import { getErrorMessage } from 'utils/Utils'
|
||||
// import { MarkdownEditorWithPreview } from 'components/MarkdownEditorWithPreview/MarkdownEditorWithPreview'
|
||||
// import css from './ReviewDecisionButton.module.scss'
|
||||
|
||||
enum PullReqReviewDecision {
|
||||
REVIEWED = 'reviewed',
|
||||
APPROVED = 'approved',
|
||||
CHANGEREQ = 'changereq'
|
||||
}
|
||||
// enum PullReqReviewDecision {
|
||||
// REVIEWED = 'reviewed',
|
||||
// APPROVED = 'approved',
|
||||
// CHANGEREQ = 'changereq'
|
||||
// }
|
||||
|
||||
interface ReviewDecisionButtonProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
||||
shouldHide: boolean
|
||||
pullRequestMetadata?: TypesPullReq
|
||||
}
|
||||
// interface ReviewDecisionButtonProps extends Pick<GitInfoProps, 'repoMetadata'> {
|
||||
// shouldHide: boolean
|
||||
// pullRequestMetadata?: TypesPullReq
|
||||
// }
|
||||
|
||||
export const ReviewDecisionButton: React.FC<ReviewDecisionButtonProps> = ({
|
||||
repoMetadata,
|
||||
pullRequestMetadata,
|
||||
shouldHide
|
||||
}) => {
|
||||
const { getString } = useStrings()
|
||||
const { showError, showSuccess } = useToaster()
|
||||
const [comment, setComment] = useState('')
|
||||
const [reset, setReset] = useState(false)
|
||||
const [decision, setDecision] = useState<PullReqReviewDecision>(PullReqReviewDecision.REVIEWED)
|
||||
const { mutate, loading } = useMutate({
|
||||
verb: 'POST',
|
||||
path: `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullRequestMetadata?.number}/reviews`
|
||||
})
|
||||
const submitReview = useCallback(() => {
|
||||
mutate({ decision, message: comment })
|
||||
.then(() => {
|
||||
setReset(true)
|
||||
showSuccess(getString('pr.reviewSubmitted'))
|
||||
})
|
||||
.catch(exception => showError(getErrorMessage(exception)))
|
||||
}, [comment, decision, mutate, showError, showSuccess, getString])
|
||||
// /**
|
||||
// * @deprecated
|
||||
// */
|
||||
// export const ReviewDecisionButton: React.FC<ReviewDecisionButtonProps> = ({
|
||||
// repoMetadata,
|
||||
// pullRequestMetadata,
|
||||
// shouldHide
|
||||
// }) => {
|
||||
// const { getString } = useStrings()
|
||||
// const { showError, showSuccess } = useToaster()
|
||||
// const [comment, setComment] = useState('')
|
||||
// const [reset, setReset] = useState(false)
|
||||
// const [decision, setDecision] = useState<PullReqReviewDecision>(PullReqReviewDecision.REVIEWED)
|
||||
// const { mutate, loading } = useMutate({
|
||||
// verb: 'POST',
|
||||
// path: `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullRequestMetadata?.number}/reviews`
|
||||
// })
|
||||
// const submitReview = useCallback(() => {
|
||||
// mutate({ decision, message: comment })
|
||||
// .then(() => {
|
||||
// setReset(true)
|
||||
// showSuccess(getString('pr.reviewSubmitted'))
|
||||
// })
|
||||
// .catch(exception => showError(getErrorMessage(exception)))
|
||||
// }, [comment, decision, mutate, showError, showSuccess, getString])
|
||||
|
||||
useEffect(() => {
|
||||
let timeoutId = 0
|
||||
if (reset) {
|
||||
timeoutId = window.setTimeout(() => setReset(false))
|
||||
}
|
||||
return () => window.clearTimeout(timeoutId)
|
||||
}, [reset])
|
||||
// useEffect(() => {
|
||||
// let timeoutId = 0
|
||||
// if (reset) {
|
||||
// timeoutId = window.setTimeout(() => setReset(false))
|
||||
// }
|
||||
// return () => window.clearTimeout(timeoutId)
|
||||
// }, [reset])
|
||||
|
||||
return (
|
||||
<Button
|
||||
text={getString('pr.reviewChanges')}
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
intent="success"
|
||||
rightIcon="chevron-down"
|
||||
className={cx(css.btn, { [css.hide]: shouldHide })}
|
||||
style={{ '--background-color': 'var(--green-800)' } as React.CSSProperties}
|
||||
tooltip={
|
||||
<Render when={!reset}>
|
||||
<Container padding="large" className={css.popup}>
|
||||
<Layout.Vertical spacing="medium">
|
||||
<Container className={css.markdown} padding="medium">
|
||||
<MarkdownEditorWithPreview
|
||||
value={comment}
|
||||
onChange={setComment}
|
||||
hideButtons
|
||||
i18n={{
|
||||
placeHolder: getString('leaveAComment'),
|
||||
tabEdit: getString('write'),
|
||||
tabPreview: getString('preview'),
|
||||
save: getString('save'),
|
||||
cancel: getString('cancel')
|
||||
}}
|
||||
editorHeight="100px"
|
||||
/>
|
||||
</Container>
|
||||
<Container padding={{ left: 'xxxlarge' }}>
|
||||
<RadioGroup>
|
||||
<Radio
|
||||
name="decision"
|
||||
defaultChecked={decision === PullReqReviewDecision.REVIEWED}
|
||||
label={getString('comment')}
|
||||
value={PullReqReviewDecision.REVIEWED}
|
||||
onChange={() => setDecision(PullReqReviewDecision.REVIEWED)}
|
||||
/>
|
||||
<Radio
|
||||
name="decision"
|
||||
defaultChecked={decision === PullReqReviewDecision.APPROVED}
|
||||
label={getString('approve')}
|
||||
value={PullReqReviewDecision.APPROVED}
|
||||
onChange={() => setDecision(PullReqReviewDecision.APPROVED)}
|
||||
/>
|
||||
<Radio
|
||||
name="decision"
|
||||
defaultChecked={decision === PullReqReviewDecision.CHANGEREQ}
|
||||
label={getString('requestChanges')}
|
||||
value={PullReqReviewDecision.CHANGEREQ}
|
||||
onChange={() => setDecision(PullReqReviewDecision.CHANGEREQ)}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Container>
|
||||
<Container>
|
||||
<Button
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
text={getString('submitReview')}
|
||||
size={ButtonSize.MEDIUM}
|
||||
onClick={submitReview}
|
||||
disabled={!(comment || '').trim().length && decision != PullReqReviewDecision.APPROVED}
|
||||
loading={loading}
|
||||
/>
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
</Container>
|
||||
</Render>
|
||||
}
|
||||
tooltipProps={{ interactionKind: 'click', position: 'bottom-right', hasBackdrop: true }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
// return (
|
||||
// <Button
|
||||
// text={getString('pr.reviewChanges')}
|
||||
// variation={ButtonVariation.PRIMARY}
|
||||
// intent="success"
|
||||
// rightIcon="chevron-down"
|
||||
// className={cx(css.btn, { [css.hide]: shouldHide })}
|
||||
// style={{ '--background-color': 'var(--green-800)' } as React.CSSProperties}
|
||||
// tooltip={
|
||||
// <Render when={!reset}>
|
||||
// <Container padding="large" className={css.popup}>
|
||||
// <Layout.Vertical spacing="medium">
|
||||
// <Container className={css.markdown} padding="medium">
|
||||
// <MarkdownEditorWithPreview
|
||||
// value={comment}
|
||||
// onChange={setComment}
|
||||
// hideButtons
|
||||
// i18n={{
|
||||
// placeHolder: getString('leaveAComment'),
|
||||
// tabEdit: getString('write'),
|
||||
// tabPreview: getString('preview'),
|
||||
// save: getString('save'),
|
||||
// cancel: getString('cancel')
|
||||
// }}
|
||||
// editorHeight="100px"
|
||||
// />
|
||||
// </Container>
|
||||
// <Container padding={{ left: 'xxxlarge' }}>
|
||||
// <RadioGroup>
|
||||
// <Radio
|
||||
// name="decision"
|
||||
// defaultChecked={decision === PullReqReviewDecision.REVIEWED}
|
||||
// label={getString('comment')}
|
||||
// value={PullReqReviewDecision.REVIEWED}
|
||||
// onChange={() => setDecision(PullReqReviewDecision.REVIEWED)}
|
||||
// />
|
||||
// <Radio
|
||||
// name="decision"
|
||||
// defaultChecked={decision === PullReqReviewDecision.APPROVED}
|
||||
// label={getString('approve')}
|
||||
// value={PullReqReviewDecision.APPROVED}
|
||||
// onChange={() => setDecision(PullReqReviewDecision.APPROVED)}
|
||||
// />
|
||||
// <Radio
|
||||
// name="decision"
|
||||
// defaultChecked={decision === PullReqReviewDecision.CHANGEREQ}
|
||||
// label={getString('requestChanges')}
|
||||
// value={PullReqReviewDecision.CHANGEREQ}
|
||||
// onChange={() => setDecision(PullReqReviewDecision.CHANGEREQ)}
|
||||
// />
|
||||
// </RadioGroup>
|
||||
// </Container>
|
||||
// <Container>
|
||||
// <Button
|
||||
// variation={ButtonVariation.PRIMARY}
|
||||
// text={getString('submitReview')}
|
||||
// size={ButtonSize.MEDIUM}
|
||||
// onClick={submitReview}
|
||||
// disabled={!(comment || '').trim().length && decision != PullReqReviewDecision.APPROVED}
|
||||
// loading={loading}
|
||||
// />
|
||||
// </Container>
|
||||
// </Layout.Vertical>
|
||||
// </Container>
|
||||
// </Render>
|
||||
// }
|
||||
// tooltipProps={{ interactionKind: 'click', position: 'bottom-right', hasBackdrop: true }}
|
||||
// />
|
||||
// )
|
||||
// }
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useResizeDetector } from 'react-resize-detector'
|
||||
import type { EditorView } from '@codemirror/view'
|
||||
import { Render, Match, Truthy, Falsy, Else } from 'react-jsx-match'
|
||||
@ -68,6 +68,7 @@ interface CommentBoxProps<T> {
|
||||
atCommentItem?: CommentItem<T>
|
||||
) => Promise<[boolean, CommentItem<T> | undefined]>
|
||||
onCancel?: () => void
|
||||
setDirty: (dirty: boolean) => void
|
||||
outlets?: Partial<Record<CommentBoxOutletPosition, React.ReactNode>>
|
||||
}
|
||||
|
||||
@ -83,12 +84,14 @@ export const CommentBox = <T = unknown,>({
|
||||
onCancel = noop,
|
||||
hideCancel,
|
||||
resetOnSave,
|
||||
setDirty: setDirtyProp,
|
||||
outlets = {}
|
||||
}: CommentBoxProps<T>) => {
|
||||
const { getString } = useStrings()
|
||||
const [comments, setComments] = useState<CommentItem<T>[]>(commentItems)
|
||||
const [showReplyPlaceHolder, setShowReplyPlaceHolder] = useState(!!comments.length)
|
||||
const [markdown, setMarkdown] = useState(initialContent)
|
||||
const [dirties, setDirties] = useState<Record<string, boolean>>({})
|
||||
const { ref } = useResizeDetector<HTMLDivElement>({
|
||||
refreshMode: 'debounce',
|
||||
handleWidth: false,
|
||||
@ -117,6 +120,13 @@ export const CommentBox = <T = unknown,>({
|
||||
}, [])
|
||||
const viewRef = useRef<EditorView>()
|
||||
|
||||
useEffect(() => {
|
||||
setDirtyProp(Object.values(dirties).some(dirty => dirty))
|
||||
return () => {
|
||||
setDirtyProp(false)
|
||||
}
|
||||
}, [dirties]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<Container
|
||||
className={cx(css.main, { [css.fluid]: fluid }, className)}
|
||||
@ -140,6 +150,9 @@ export const CommentBox = <T = unknown,>({
|
||||
|
||||
return [result, updatedItem]
|
||||
}}
|
||||
setDirty={(index, dirty) => {
|
||||
setDirties({ ...dirties, [index]: dirty })
|
||||
}}
|
||||
outlets={outlets}
|
||||
/>
|
||||
<Match expr={showReplyPlaceHolder}>
|
||||
@ -199,6 +212,9 @@ export const CommentBox = <T = unknown,>({
|
||||
}}
|
||||
onCancel={_onCancel}
|
||||
hideCancel={hideCancel}
|
||||
setDirty={_dirty => {
|
||||
setDirties({ ...dirties, ['new']: _dirty })
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
</Falsy>
|
||||
@ -211,12 +227,14 @@ export const CommentBox = <T = unknown,>({
|
||||
|
||||
interface CommentsThreadProps<T> extends Pick<CommentBoxProps<T>, 'commentItems' | 'handleAction' | 'outlets'> {
|
||||
onQuote: (content: string) => void
|
||||
setDirty: (index: number, dirty: boolean) => void
|
||||
}
|
||||
|
||||
const CommentsThread = <T = unknown,>({
|
||||
onQuote,
|
||||
commentItems = [],
|
||||
handleAction,
|
||||
setDirty,
|
||||
outlets = {}
|
||||
}: CommentsThreadProps<T>) => {
|
||||
const { getString } = useStrings()
|
||||
@ -331,6 +349,9 @@ const CommentsThread = <T = unknown,>({
|
||||
}
|
||||
}}
|
||||
onCancel={() => resetStateAtIndex(index)}
|
||||
setDirty={_dirty => {
|
||||
setDirty(index, _dirty)
|
||||
}}
|
||||
i18n={{
|
||||
placeHolder: getString('leaveAComment'),
|
||||
tabEdit: getString('write'),
|
||||
|
@ -28,6 +28,7 @@ import type { OpenapiCommentCreatePullReqRequest, TypesPullReq, TypesPullReqActi
|
||||
import { getErrorMessage } from 'utils/Utils'
|
||||
import { CopyButton } from 'components/CopyButton/CopyButton'
|
||||
import { AppWrapper } from 'App'
|
||||
import { NavigationCheck } from 'components/NavigationCheck/NavigationCheck'
|
||||
import {
|
||||
activitiesToDiffCommentItems,
|
||||
activityToCommentItem,
|
||||
@ -91,6 +92,7 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
|
||||
const { mutate: updateComment } = useMutate({ verb: 'PATCH', path: ({ id }) => `${path}/${id}` })
|
||||
const { mutate: deleteComment } = useMutate({ verb: 'DELETE', path: ({ id }) => `${path}/${id}` })
|
||||
const [comments, setComments] = useState<DiffCommentItem<TypesPullReqActivity>[]>(activitiesToDiffCommentItems(diff))
|
||||
const [dirty, setDirty] = useState(false)
|
||||
const commentsRef = useRef<DiffCommentItem<TypesPullReqActivity>[]>(comments)
|
||||
const setContainerRef = useCallback(
|
||||
node => {
|
||||
@ -298,6 +300,7 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
|
||||
delete lineInfo.rowElement.dataset.annotated
|
||||
setTimeout(() => setComments(commentsRef.current.filter(item => item !== comment)), 0)
|
||||
}}
|
||||
setDirty={setDirty}
|
||||
currentUserName={currentUser.display_name}
|
||||
handleAction={async (action, value, commentItem) => {
|
||||
let result = true
|
||||
@ -511,6 +514,7 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
|
||||
</Render>
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
<NavigationCheck when={dirty} />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ interface MarkdownEditorWithPreviewProps {
|
||||
onChange?: (value: string) => void
|
||||
onSave?: (value: string) => void
|
||||
onCancel?: () => void
|
||||
setDirty: (dirty: boolean) => void
|
||||
i18n: {
|
||||
placeHolder: string
|
||||
tabEdit: string
|
||||
@ -59,6 +60,7 @@ export function MarkdownEditorWithPreview({
|
||||
onChange,
|
||||
onSave,
|
||||
onCancel,
|
||||
setDirty: setDirtyProp,
|
||||
i18n,
|
||||
hideButtons,
|
||||
hideCancel,
|
||||
@ -186,6 +188,14 @@ export function MarkdownEditorWithPreview({
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setDirtyProp?.(dirty)
|
||||
|
||||
return () => {
|
||||
setDirtyProp?.(false)
|
||||
}
|
||||
}, [dirty]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
useEffect(() => {
|
||||
if (viewRefProp) {
|
||||
viewRefProp.current = viewRef.current
|
||||
|
@ -0,0 +1,7 @@
|
||||
.main {
|
||||
padding-top: var(--spacing-xxlarge) !important;
|
||||
|
||||
p {
|
||||
word-break: normal;
|
||||
}
|
||||
}
|
6
web/src/components/NavigationCheck/NavigationCheck.module.scss.d.ts
vendored
Normal file
6
web/src/components/NavigationCheck/NavigationCheck.module.scss.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/* eslint-disable */
|
||||
// this is an auto-generated file
|
||||
declare const styles: {
|
||||
readonly main: string
|
||||
}
|
||||
export default styles
|
56
web/src/components/NavigationCheck/NavigationCheck.tsx
Normal file
56
web/src/components/NavigationCheck/NavigationCheck.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import { Prompt, useHistory } from 'react-router-dom'
|
||||
import { useConfirmationDialog } from '@harness/uicore'
|
||||
import { Intent } from '@harness/design-system'
|
||||
import type * as History from 'history'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import css from './NavigationCheck.module.scss'
|
||||
|
||||
interface NavigationCheckProps {
|
||||
when?: boolean
|
||||
i18n?: {
|
||||
message?: string
|
||||
title?: string
|
||||
confirmText?: string
|
||||
cancelText?: string
|
||||
}
|
||||
replace?: boolean
|
||||
}
|
||||
|
||||
export const NavigationCheck: React.FC<NavigationCheckProps> = ({ when, i18n, replace }) => {
|
||||
const history = useHistory()
|
||||
const [lastLocation, setLastLocation] = useState<History.Location | null>(null)
|
||||
const [confirmed, setConfirmed] = useState(false)
|
||||
const { getString } = useStrings()
|
||||
const { openDialog } = useConfirmationDialog({
|
||||
cancelButtonText: i18n?.cancelText || getString('unsavedChanges.stay'),
|
||||
showCloseButton: false,
|
||||
contentText: i18n?.message || getString('unsavedChanges.message'),
|
||||
titleText: i18n?.title || getString('unsavedChanges.title'),
|
||||
confirmButtonText: i18n?.confirmText || getString('unsavedChanges.leave'),
|
||||
intent: Intent.WARNING,
|
||||
onCloseDialog: setConfirmed,
|
||||
className: css.main
|
||||
})
|
||||
const prompt = useCallback(
|
||||
(nextLocation: History.Location): string | boolean => {
|
||||
if (!confirmed) {
|
||||
openDialog()
|
||||
setLastLocation(nextLocation)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
[confirmed, openDialog]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (confirmed && lastLocation) {
|
||||
history[replace ? 'replace' : 'push'](lastLocation.pathname + lastLocation.search)
|
||||
}
|
||||
|
||||
confirmed && setConfirmed(false)
|
||||
}, [confirmed, lastLocation, history, replace])
|
||||
|
||||
return <Prompt when={when} message={prompt} />
|
||||
}
|
@ -302,6 +302,10 @@ export interface StringsMap {
|
||||
tags: string
|
||||
title: string
|
||||
tooltipRepoEdit: string
|
||||
'unsavedChanges.leave': string
|
||||
'unsavedChanges.message': string
|
||||
'unsavedChanges.stay': string
|
||||
'unsavedChanges.title': string
|
||||
updateFile: string
|
||||
updateWebhook: string
|
||||
updated: string
|
||||
|
@ -377,3 +377,8 @@ viewRaw: View Raw
|
||||
download: Download
|
||||
changes: Changes
|
||||
contents: Contents
|
||||
unsavedChanges:
|
||||
title: Close without saving?
|
||||
message: 'You have unsaved changes. Are you sure you want to leave this page without saving?'
|
||||
leave: Leave this Page
|
||||
stay: Stay on this Page
|
||||
|
@ -33,6 +33,7 @@ import { CommentAction, CommentBox, CommentBoxOutletPosition, CommentItem } from
|
||||
import { useConfirmAct } from 'hooks/useConfirmAction'
|
||||
import { commentState, formatDate, formatTime, getErrorMessage, orderSortDate, dayAgoInMS } from 'utils/Utils'
|
||||
import { activityToCommentItem, CommentType, DIFF2HTML_CONFIG, ViewStyle } from 'components/DiffViewer/DiffViewerUtils'
|
||||
import { NavigationCheck } from 'components/NavigationCheck/NavigationCheck'
|
||||
import { ThreadSection } from 'components/ThreadSection/ThreadSection'
|
||||
import { PullRequestTabContentWrapper } from '../PullRequestTabContentWrapper'
|
||||
import { DescriptionBox } from './DescriptionBox'
|
||||
@ -175,6 +176,8 @@ export const Conversation: React.FC<ConversationProps> = ({
|
||||
const { mutate: deleteComment } = useMutate({ verb: 'DELETE', path: ({ id }) => `${path}/${id}` })
|
||||
const confirmAct = useConfirmAct()
|
||||
const [commentCreated, setCommentCreated] = useState(false)
|
||||
const [dirtyNewComment, setDirtyNewComment] = useState(false)
|
||||
const [dirtyCurrentComments, setDirtyCurrentComments] = useState(false)
|
||||
|
||||
const refreshPR = useCallback(() => {
|
||||
onCommentUpdate()
|
||||
@ -316,6 +319,7 @@ export const Conversation: React.FC<ConversationProps> = ({
|
||||
})}
|
||||
commentItems={commentItems}
|
||||
currentUserName={currentUser.display_name}
|
||||
setDirty={setDirtyCurrentComments}
|
||||
handleAction={async (action, value, commentItem) => {
|
||||
let result = true
|
||||
let updatedItem: CommentItem<TypesPullReqActivity> | undefined = undefined
|
||||
@ -411,6 +415,7 @@ export const Conversation: React.FC<ConversationProps> = ({
|
||||
currentUserName={currentUser.display_name}
|
||||
resetOnSave
|
||||
hideCancel
|
||||
setDirty={setDirtyNewComment}
|
||||
handleAction={async (_action, value) => {
|
||||
let result = true
|
||||
let updatedItem: CommentItem<TypesPullReqActivity> | undefined = undefined
|
||||
@ -440,6 +445,7 @@ export const Conversation: React.FC<ConversationProps> = ({
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
</Container>
|
||||
<NavigationCheck when={dirtyCurrentComments || dirtyNewComment} />
|
||||
</PullRequestTabContentWrapper>
|
||||
)
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { useStrings } from 'framework/strings'
|
||||
import type { OpenapiUpdatePullReqRequest } from 'services/code'
|
||||
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
|
||||
import { MarkdownEditorWithPreview } from 'components/MarkdownEditorWithPreview/MarkdownEditorWithPreview'
|
||||
import { NavigationCheck } from 'components/NavigationCheck/NavigationCheck'
|
||||
import { getErrorMessage } from 'utils/Utils'
|
||||
import type { ConversationProps } from './Conversation'
|
||||
import css from './Conversation.module.scss'
|
||||
@ -17,7 +18,7 @@ export const DescriptionBox: React.FC<ConversationProps> = ({
|
||||
onCommentUpdate: refreshPullRequestMetadata
|
||||
}) => {
|
||||
const [edit, setEdit] = useState(false)
|
||||
// const [updated, setUpdated] = useState(pullRequestMetadata.edited as number)
|
||||
const [dirty, setDirty] = useState(false)
|
||||
const [originalContent, setOriginalContent] = useState(pullRequestMetadata.description as string)
|
||||
const [content, setContent] = useState(originalContent)
|
||||
const { getString } = useStrings()
|
||||
@ -52,6 +53,7 @@ export const DescriptionBox: React.FC<ConversationProps> = ({
|
||||
setContent(originalContent)
|
||||
setEdit(false)
|
||||
}}
|
||||
setDirty={setDirty}
|
||||
i18n={{
|
||||
placeHolder: getString('pr.enterDesc'),
|
||||
tabEdit: getString('write'),
|
||||
@ -84,6 +86,7 @@ export const DescriptionBox: React.FC<ConversationProps> = ({
|
||||
</Container>
|
||||
)}
|
||||
</Container>
|
||||
<NavigationCheck when={dirty} />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ 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 { NavigationCheck } from 'components/NavigationCheck/NavigationCheck'
|
||||
import css from './FileEditor.module.scss'
|
||||
|
||||
interface EditorProps extends Pick<GitInfoProps, 'repoMetadata' | 'gitRef' | 'resourcePath'> {
|
||||
@ -97,6 +98,11 @@ function Editor({ resourceContent, repoMetadata, gitRef, resourcePath, isReposit
|
||||
verifyFolder().then(() => setStartVerifyFolder(true))
|
||||
}, [fileName, parentPath, language, content, verifyFolder])
|
||||
const [selectedView, setSelectedView] = useState(VisualYamlSelectedView.VISUAL)
|
||||
const [dirty, setDirty] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setDirty(!(!fileName || (isUpdate && content === originalContent)))
|
||||
}, [fileName, isUpdate, content, originalContent])
|
||||
|
||||
// Calculate file name input field width based on number of characters inside
|
||||
useEffect(() => {
|
||||
@ -194,7 +200,7 @@ function Editor({ resourceContent, repoMetadata, gitRef, resourcePath, isReposit
|
||||
<CommitModalButton
|
||||
text={getString('commitChanges')}
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
disabled={!fileName || (isUpdate && content === originalContent)}
|
||||
disabled={!dirty}
|
||||
repoMetadata={repoMetadata}
|
||||
commitAction={commitAction}
|
||||
commitTitlePlaceHolder={getString(isNew ? 'createFile' : isUpdate ? 'updateFile' : 'renameFile')
|
||||
@ -206,6 +212,8 @@ function Editor({ resourceContent, repoMetadata, gitRef, resourcePath, isReposit
|
||||
payload={content}
|
||||
sha={resourceContent?.sha}
|
||||
onSuccess={(_data, newBranch) => {
|
||||
setDirty(false)
|
||||
|
||||
if (newBranch) {
|
||||
history.replace(
|
||||
routes.toCODECompare({
|
||||
@ -259,6 +267,7 @@ function Editor({ resourceContent, repoMetadata, gitRef, resourcePath, isReposit
|
||||
<DiffEditor language={language} original={originalContent} source={content} onChange={setContent} />
|
||||
)}
|
||||
</Container>
|
||||
<NavigationCheck when={dirty} />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user