Skip to content

Render the overview page with React #1096

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 59 commits into from
May 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
ff6a26b
WIP: Bring in React and start converting the overview page.
Feb 21, 2019
8dd1999
WIP: Spaces are hard.
Feb 21, 2019
c5b75c6
wip
Mar 8, 2019
ff4af4d
WHITESPACE
Mar 14, 2019
ab42049
Diffs render pretty well.
Mar 14, 2019
6030967
Review comments are rendering, things looking pretty good.
Mar 14, 2019
2880fb0
Pull request mutation context.
Mar 29, 2019
f8e408b
Preserve webview state in cache.
Mar 29, 2019
500a851
WIP: Comments and status checks.
Mar 30, 2019
e8fbaed
Merged status checks from master.
Mar 30, 2019
5383098
Status check text and icons.
Mar 30, 2019
1d5bee7
Show and hide status checks.
Mar 30, 2019
8659e85
WIP: Merge selector.
Mar 30, 2019
22765d8
WIP: Merge option selector flow.
Mar 30, 2019
10f00a7
Merge flow working.
Mar 30, 2019
3b7e12f
Tidying up, fixed styling for the submit merge button.
Apr 1, 2019
7be982f
Commenting widget.
Apr 1, 2019
7560fa9
Separate out views into their own files.
Apr 1, 2019
bd0b044
Rename actions.tsx to context.tsx, which is rather more fitting.
Apr 1, 2019
03d24e2
Remove unneeded code.
Apr 1, 2019
1539799
Rename views.tsx to overview.tsx
Apr 1, 2019
8999d88
Add MergedEvent to Timeline view.
Apr 1, 2019
3f79b64
Add Merged Events to Timeline view.
queerviolet Apr 1, 2019
666ed3d
Sidebar with reviewers and labels.
Apr 8, 2019
296c8d5
Action bar for editing and deleting comments.
Apr 8, 2019
405a9ff
Rebase from upstream.
Apr 8, 2019
b008496
Rebase atop master.
Apr 18, 2019
d3df91c
Merge branch 'master' into qv/react
Apr 18, 2019
0f2a2ac
Merge branch 'fixes/github-enterprise-user-avatar' into qv/react
StanleyGoldman Apr 18, 2019
04c550c
Fix state management and styling.
Apr 19, 2019
fca5188
Clicking on comments now brings us to the file.
Apr 19, 2019
af1f7a7
Hide status checks section if there aren't any.
Apr 19, 2019
c7cebbe
Use a switch for StateIcon and remove pullRequestOverviewRenderer.
Apr 19, 2019
c253a3d
Update header to not reference PROverviewRenderer
Apr 19, 2019
c1b6455
Rebase atop master, and fix action bar actions:
Apr 19, 2019
3448353
Editing review comments now saves properly.
Apr 22, 2019
9dcbb6f
Comment editing and submit button hover.
Apr 22, 2019
7e0343d
Removing labels and reviewers.
Apr 22, 2019
ca37507
Fix lint errors and use edited body markdown.
Apr 30, 2019
2912cee
Revert staging server in githubServer.ts
Apr 30, 2019
545e057
Edit pr titles
Apr 30, 2019
3fd16c5
Handle failures during comment submission.
May 2, 2019
99e39dd
Default the status check view to open if any checks have failed.
May 3, 2019
940a2ed
Fix title editing styles and ensure validation passes before setting …
May 3, 2019
863eb80
Finally fix comment drafts with refs.
May 3, 2019
80e216c
Fix disabled state on submit inputs and fix disabling of request chan…
May 3, 2019
370f293
Merge branch 'master' into qv/react
May 6, 2019
12bb65c
Merge branch 'master' into qv/react
May 6, 2019
f813283
Invert default avatar icons when the theme is light.
May 6, 2019
42e5c8b
Fix spacing form spacing and alignment.
May 6, 2019
3992696
Toggle the status checks open state when statuses update.
May 6, 2019
5007102
Retain comment frame when editing standalone comments.
May 6, 2019
8b4f1b7
Checking if the repository returns avatars
StanleyGoldman May 6, 2019
052642d
Update PR description correctly.
May 6, 2019
f52df57
Merge pull request #1146 from microsoft/enterprise-avatar-on-create-c…
StanleyGoldman May 7, 2019
25728a1
Clear pendingCommentDrafts for comment on cancel.
May 7, 2019
4a7c7d7
Fix removing reviewers.
May 7, 2019
a06c294
Merge branch 'qv/react' of github.com:Microsoft/vscode-pull-request-g…
May 7, 2019
e69d060
Merge branch 'master' into qv/react
May 8, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,8 @@
"@types/node": "*",
"@types/node-fetch": "^2.1.4",
"@types/query-string": "^6.1.1",
"@types/react": "^16.8.4",
"@types/react-dom": "^16.8.2",
"@types/webpack": "^4.4.10",
"@types/ws": "^5.1.2",
"css-loader": "^0.28.11",
Expand All @@ -537,6 +539,8 @@
"gulp-util": "^3.0.8",
"minimist": "^1.2.0",
"mocha": "^5.2.0",
"react": "^16.8.2",
"react-dom": "^16.8.2",
"style-loader": "^0.21.0",
"svg-inline-loader": "^0.8.0",
"ts-loader": "^4.0.1",
Expand Down
22 changes: 22 additions & 0 deletions preview-src/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as React from 'react';
import { useContext, useState, useEffect } from 'react';
import { render } from 'react-dom';
import { Overview } from './overview';
import PullRequestContext from './context';
import { PullRequest } from './cache';

export function main() {
render(
<Root>{pr => <Overview {...pr} />}</Root>
, document.getElementById('app'));
}

function Root({ children }) {
const ctx = useContext(PullRequestContext);
const [pr, setPR] = useState<PullRequest>(ctx.pr);
useEffect(() => {
ctx.onchange = setPR;
setPR(ctx.pr);
}, []);
return pr ? children(pr) : 'Loading...';
}
11 changes: 5 additions & 6 deletions preview-src/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,18 @@ export interface PullRequest {
}

export function getState(): PullRequest {
return vscode.getState() || {};
return vscode.getState();
}

export function setState(pullRequest: PullRequest): void {
let oldPullRequest = getState();

if (oldPullRequest.number && oldPullRequest.number === pullRequest.number) {
pullRequest = Object.assign(pullRequest, {
pendingCommentText: oldPullRequest.pendingCommentText
});
if (oldPullRequest &&
oldPullRequest.number && oldPullRequest.number === pullRequest.number) {
pullRequest.pendingCommentText = oldPullRequest.pendingCommentText;
}

vscode.setState(pullRequest);
if (pullRequest) { vscode.setState(pullRequest); }
}

export function updateState(data: Partial<PullRequest>): void {
Expand Down
195 changes: 195 additions & 0 deletions preview-src/comment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import * as React from 'react';
import { useContext, useState, useEffect, useRef } from 'react';

import Markdown from './markdown';
import { Spaced, nbsp } from './space';
import { Avatar, AuthorLink } from './user';
import Timestamp from './timestamp';
import { Comment } from '../src/common/comment';
import { PullRequest } from './cache';
import PullRequestContext from './context';
import { editIcon, deleteIcon } from './icon';

export type Props = Partial<Comment & PullRequest> & {
headerInEditMode?: boolean
isPRDescription?: boolean
};

export function CommentView(comment: Props) {
const { id, pullRequestReviewId, canEdit, canDelete, bodyHTML, body, isPRDescription } = comment;
const [ bodyMd, setBodyMd ] = useState(body);
const { deleteComment, editComment, setDescription, pr } = useContext(PullRequestContext);
const currentDraft = pr.pendingCommentDrafts && pr.pendingCommentDrafts[id];
const [inEditMode, setEditMode] = useState(!!currentDraft);
const [showActionBar, setShowActionBar] = useState(false);

useEffect(() => {
if (body !== bodyMd) {
setBodyMd(body);
}
}, [body]);

if (inEditMode) {
return React.cloneElement(
comment.headerInEditMode
? <CommentBox for={comment} /> : <></>, {}, [
<EditComment id={id}
body={currentDraft || body}
onCancel={
() => {
if (pr.pendingCommentDrafts) {
delete pr.pendingCommentDrafts[id];
}
setEditMode(false);
}
}
onSave={
async text => {
try {
if (isPRDescription) {
await setDescription(text);
} else {
await editComment({ comment: comment as Comment, text });
}
setBodyMd(text);
} finally {
setEditMode(false);
}
}
} />
]);
}

return <CommentBox
for={comment}
onMouseEnter={() => setShowActionBar(true)}
onMouseLeave={() => setShowActionBar(false)}
>{ ((canEdit || canDelete) && showActionBar)
? <div className='action-bar comment-actions'>
{canEdit ? <button onClick={() => setEditMode(true)}>{editIcon}</button> : null}
{canDelete ? <button onClick={() => deleteComment({ id, pullRequestReviewId })}>{deleteIcon}</button> : null}
</div>
: null
}
<CommentBody bodyHTML={bodyHTML} body={bodyMd} />
</CommentBox>;
}

type CommentBoxProps = {
for: Partial<Comment & PullRequest>
header?: React.ReactChild
onMouseEnter?: any
onMouseLeave?: any
children?: any
};

function CommentBox({
for: comment,
onMouseEnter, onMouseLeave, children }: CommentBoxProps) {
const { user, author, createdAt, htmlUrl } = comment;
console.log('comment=', comment)
return <div className='comment-container comment review-comment'
{...{onMouseEnter, onMouseLeave}}
>
<div className='review-comment-container'>
<div className='review-comment-header'>
<Spaced>
<Avatar for={user || author} />
<AuthorLink for={user || author} />
{
createdAt
? <>
commented{nbsp}
<Timestamp href={htmlUrl} date={createdAt} />
</>
: <em>pending</em>
}
</Spaced>
</div>
{children}
</div>
</div>;
}

function EditComment({ id, body, onCancel, onSave }: { id: number, body: string, onCancel: () => void, onSave: (body: string) => void}) {
const draftComment = useRef<{body: string, dirty: boolean}>({ body, dirty: false });
const { updateDraft } = useContext(PullRequestContext);
useEffect(() => {
const interval = setInterval(
() => {
if (draftComment.current.dirty) {
updateDraft(id, draftComment.current.body);
draftComment.current.dirty = false;
}
},
500);
return () => clearInterval(interval);
});
return <form onSubmit={
event => {
event.preventDefault();
const { markdown }: any = event.target;
onSave(markdown.value);
}
}>
<textarea
name='markdown'
defaultValue={body}
onInput={
e => {
draftComment.current.body = (e.target as any).value;
draftComment.current.dirty = true;
}
}
/>
<div className='form-actions'>
<button className='secondary' onClick={onCancel}>Cancel</button>
<input type='submit' value='Save' />
</div>
</form>;
}

export interface Embodied {
bodyHTML?: string;
body?: string;
}

export const CommentBody = ({ bodyHTML, body }: Embodied) =>
body
? <Markdown className='comment-body' src={body} />
:
bodyHTML
? <div className='comment-body'
dangerouslySetInnerHTML={ {__html: bodyHTML }} />
:
<div className='comment-body'><em>No description provided.</em></div>;

export function AddComment({ pendingCommentText }: PullRequest) {
const { updatePR, comment } = useContext(PullRequestContext);
return <form id='comment-form' className='comment-form main-comment-form' onSubmit={onSubmit}>
<textarea id='comment-textarea'
name='body'
onInput={({ target }) =>
updatePR({ pendingCommentText: (target as any).value })}
value={pendingCommentText}
placeholder='Leave a comment' />
<div className='form-actions'>
<button id='close' className='secondary'>Close Pull Request</button>
<button id='request-changes'
disabled={!pendingCommentText}
className='secondary'>Request Changes</button>
<button id='approve'
className='secondary'>Approve</button>
<input id='reply'
value='Comment'
type='submit'
className='reply-button'
disabled={!pendingCommentText} />
</div>
</form>;

function onSubmit(evt) {
evt.preventDefault();
comment((evt.target as any).body.value);
}
}
Loading