Skip to content

Commit f9ea525

Browse files
committed
feat: add custom editor
1 parent 09371fd commit f9ea525

File tree

9 files changed

+189
-55
lines changed

9 files changed

+189
-55
lines changed

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
{
2222
"viewType": "customEditor.mrDetail",
2323
"displayName": "MR Detail",
24-
"selector": [{
24+
"selector": [
25+
{
2526
"fileNamePattern": "hosts"
26-
}],
27+
}
28+
],
2729
"priority": "default"
2830
}
2931
],

src/customEditors/mergeRequest.ts

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ export default class MRCustomEditorProvider extends CustomEditorProvider {
2626
this.panel = null;
2727
}
2828

29-
listen() {
30-
this.panel.webView.onDidReceiveMessage((message: IMessage) => {
29+
listen(webViewPanel: IWebviewPanel) {
30+
webViewPanel.webView.onDidReceiveMessage((message: IMessage) => {
3131
console.log('webview receive message => ', message);
3232
const { command, data } = message;
3333

@@ -44,25 +44,42 @@ export default class MRCustomEditorProvider extends CustomEditorProvider {
4444
}
4545
});
4646

47-
this.panel.onDidDispose(function () {
47+
webViewPanel.onDidDispose(function () {
4848
console.log('custom editor disposed');
4949
});
5050
}
5151

5252
update(data: any) {
53-
const webview = this.panel.webView;
53+
if (!this.panel) {
54+
setTimeout(() => {
55+
this.update(data);
56+
}, 500);
57+
return;
58+
}
59+
const webview = this.panel?.webView;
5460
const fileInfo = hx.Uri.file(path.resolve(__dirname, '../../out/webviews/main.js'));
5561

62+
const config = hx.workspace.getConfiguration();
63+
const colorScheme = config.get('editor.colorScheme');
64+
65+
const COLORS: Record<string, string> = {
66+
Monokai: 'themeDark',
67+
'Atom One Dark': 'themeDarkBlue',
68+
Default: 'themeLight',
69+
};
70+
5671
webview.html = `
57-
<body>
58-
<div>
59-
<div id='root'></div>
60-
</div>
61-
<script>
62-
window.__CODING__ = '${JSON.stringify(data)}'
63-
</script>
64-
<script src='${fileInfo}'></script>
65-
</body>
72+
<html>
73+
<body class='${COLORS[colorScheme]}'>
74+
<div>
75+
<div id='root'></div>
76+
</div>
77+
<script>
78+
window.__CODING__ = '${JSON.stringify(data)}'
79+
</script>
80+
<script src='${fileInfo}'></script>
81+
</body>
82+
</html>
6683
`;
6784
}
6885

@@ -72,7 +89,7 @@ export default class MRCustomEditorProvider extends CustomEditorProvider {
7289

7390
resolveCustomEditor(document: any, webViewPanel: IWebviewPanel) {
7491
this.panel = webViewPanel;
75-
this.listen();
92+
this.listen(webViewPanel);
7693
}
7794

7895
saveCustomDocument(document: any) {

src/extension.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,24 @@ import ACTIONS, { dispatch } from './utils/actions';
44
import { proxyCtx } from './utils/proxy';
55
import toast from './utils/toast';
66

7-
const accessToken = '7e4d9d17f87875e731d536d13635a700ddf52b12';
7+
// const accessToken = '7e4d9d17f87875e731d536d13635a700ddf52b12';
8+
// const user = {
9+
// id: 8005956,
10+
// avatar: 'https://coding-net-production-static-ci.codehub.cn/WM-TEXT-AVATAR-lvVeBfbGLtCPdcsAOPod.jpg',
11+
// global_key: 'PDCOwrBjib',
12+
// name: 'uniquemo',
13+
// path: '/u/PDCOwrBjib',
14+
// team: 'uniquemo',
15+
// };
16+
17+
const accessToken = '6e15bbb9960810111c90086a6efc07a923dea5a3';
818
const user = {
9-
id: 8005956,
19+
id: 8003868,
1020
avatar: 'https://coding-net-production-static-ci.codehub.cn/WM-TEXT-AVATAR-lvVeBfbGLtCPdcsAOPod.jpg',
11-
global_key: 'PDCOwrBjib',
12-
name: 'uniquemo',
13-
path: '/u/PDCOwrBjib',
14-
team: 'uniquemo',
21+
global_key: 'dHzOCagiSb',
22+
name: '莫泳欣',
23+
path: '/u/dHzOCagiSb',
24+
team: 'codingcorp',
1525
};
1626

1727
async function activate(context: IContext) {

src/init.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@ export function registerCommands(context: IContext) {
1919
const matchRes = mrItem.path.match(/\/p\/([^/]+)\/d\/([^/]+)\/git\/merge\/([0-9]+)/);
2020
if (matchRes) {
2121
const [, project, repo, mergeRequestIId] = matchRes;
22-
const result = await codingServer.getMrDetail({ team, project, repo, mergeRequestIId });
2322
context.mrCustomEditor.update({
2423
session: codingServer.session,
25-
mrItem: result,
24+
mergeRequestIId,
2625
repoInfo: {
2726
team,
2827
project,

src/typings/common.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export interface ISessionData {
2727

2828
export interface IReviewer {
2929
reviewer: IUserInfo;
30+
value: number;
31+
volunteer: string;
3032
}
3133

3234
export interface IMRItem {

webpack.config.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ module.exports = {
88
path: path.resolve(__dirname, 'out/webviews'),
99
filename: '[name].js',
1010
},
11-
// devtool: 'cheap-module-source-map',
1211
resolve: {
1312
extensions: ['.js', '.ts', '.tsx', '.json'],
1413
},

webviews/App.tsx

Lines changed: 91 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
import React, { useState } from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import cn from 'classnames';
33

4-
import { closeMergeRequest, mergeMergeRequest, allowMerge } from './services';
4+
import {
5+
getMergeRequestDetail,
6+
closeMergeRequest,
7+
mergeMergeRequest,
8+
allowMerge,
9+
disallowMerge,
10+
getReviewers
11+
} from './services';
512
import { MERGE_STATUS_TEXT, MERGE_STATUS } from './constants';
613
import style from './style.css';
714

@@ -14,20 +21,58 @@ const toast = (msg: string) => {
1421

1522
const App = () => {
1623
const data = JSON.parse(window.__CODING__);
17-
const { session, mrItem, repoInfo } = data;
24+
const { session, repoInfo, mergeRequestIId } = data;
1825
const { accessToken } = session;
1926
const user = session?.user;
2027
const team = user?.team;
2128

22-
const { can_merge: canMerge, merge_request: mergeRequest } = mrItem;
23-
const { title, srcBranch, desBranch, author, merge_status: mergeStatus } = mergeRequest;
24-
const url = `https://${team}.coding.net${mergeRequest.path}`;
25-
2629
const [isClosing, setIsClosing] = useState(false);
2730
const [isMerging, setIsMerging] = useState(false);
2831
const [isAllowing, setIsAllowing] = useState(false);
29-
const [mrStatus, setMrStatus] = useState(mergeStatus);
30-
const [allowText, setAllowText] = useState('允许合并');
32+
const [isDisAllowing, setIsDisAllowing] = useState(false);
33+
const [mrStatus, setMrStatus] = useState<MERGE_STATUS>(MERGE_STATUS.CANMERGE);
34+
const [mrDetail, setMrDetail] = useState();
35+
const [reviewers, setReviewers] = useState<{ reviewers: any[], volunteer_reviewers: any[] }>({ reviewers: [], volunteer_reviewers: [] });
36+
37+
const index = reviewers.reviewers.findIndex((r) => r.reviewer.id === user.id);
38+
let agreed = true;
39+
if (index >= 0) {
40+
agreed = reviewers.reviewers[index].value === 100;
41+
} else {
42+
agreed = reviewers.volunteer_reviewers.findIndex((r) => r.reviewer.id === user.id) >= 0;
43+
}
44+
45+
const [isAgreed, setIsAgreed] = useState(agreed);
46+
47+
const getParams = () => ({
48+
...repoInfo,
49+
mergeRequestIId
50+
});
51+
52+
useEffect(() => {
53+
const asyncFn = async () => {
54+
const [detailRes, reviewersRes] = await Promise.all([
55+
getMergeRequestDetail(accessToken, { ...getParams() }),
56+
getReviewers(accessToken, { ...getParams() })
57+
]);
58+
59+
if (!reviewersRes.code) {
60+
setReviewers(reviewersRes.data);
61+
}
62+
63+
if (!detailRes.code) {
64+
setMrDetail(detailRes.data);
65+
setMrStatus(detailRes.data.merge_request.merge_status);
66+
}
67+
};
68+
asyncFn();
69+
}, []);
70+
71+
if (!mrDetail) return null;
72+
73+
const { can_merge: canMerge, merge_request: mergeRequest } = mrDetail as any;
74+
const { title, srcBranch, desBranch, author, body } = mergeRequest;
75+
const url = `https://${team}.coding.net${mergeRequest.path}`;
3176

3277
const viewOnWeb = () => {
3378
window.hbuilderx.postMessage({
@@ -40,10 +85,7 @@ const App = () => {
4085
if (isClosing) return;
4186

4287
setIsClosing(true);
43-
const result = await closeMergeRequest(accessToken, {
44-
...repoInfo,
45-
mergeRequestIId: mergeRequest.iid
46-
});
88+
const result = await closeMergeRequest(accessToken, { ...getParams() });
4789

4890
if (!result.code) {
4991
setMrStatus(MERGE_STATUS.REFUSED);
@@ -59,8 +101,7 @@ const App = () => {
59101

60102
setIsMerging(true);
61103
const result = await mergeMergeRequest(accessToken, {
62-
...repoInfo,
63-
mergeRequestIId: mergeRequest.iid,
104+
...getParams(),
64105
message: `
65106
Accept Merge Request #${mergeRequest.iid}: (${srcBranch} -> ${desBranch})
66107
Merge Request: ${title}
@@ -83,20 +124,32 @@ const App = () => {
83124
if (isAllowing) return;
84125

85126
setIsAllowing(true);
86-
const result = await allowMerge(accessToken, {
87-
...repoInfo,
88-
mergeRequestIId: mergeRequest.iid
89-
});
127+
const result = await allowMerge(accessToken, { ...getParams() });
90128

91129
if (!result.code) {
92-
setAllowText('已允许');
130+
setIsAgreed(true);
93131
} else {
94132
toast('操作失败');
95133
}
96134

97135
setIsAllowing(false);
98136
};
99137

138+
const handleDisAllowMerge = async () => {
139+
if (isDisAllowing) return;
140+
141+
setIsDisAllowing(true);
142+
const result = await disallowMerge(accessToken, { ...getParams() });
143+
144+
if (!result.code) {
145+
setIsAgreed(false);
146+
} else {
147+
toast('操作失败');
148+
}
149+
150+
setIsDisAllowing(false);
151+
};
152+
100153
const renderStatus = () => {
101154
const CNS = {
102155
[MERGE_STATUS.CANMERGE]: style.success,
@@ -106,6 +159,8 @@ const App = () => {
106159
return <span className={cn(style.status, CNS[mrStatus])}>{MERGE_STATUS_TEXT[mrStatus]}</span>;
107160
};
108161

162+
const renderActionText = (loading: boolean, text: string) => loading ? `${text}中...` : text;
163+
109164
const showCloseBtn = (canMerge || user.id === mergeRequest.author.id) && mrStatus !== MERGE_STATUS.REFUSED && mrStatus !== MERGE_STATUS.ACCEPTED;
110165
const mrStatusOk = mrStatus === MERGE_STATUS.CANMERGE || mrStatus === MERGE_STATUS.CANNOTMERGE;
111166
const showMergeBtn = mrStatus === MERGE_STATUS.CANMERGE;
@@ -121,22 +176,34 @@ const App = () => {
121176
<div>{`将分支 ${srcBranch} 合并到分支 ${desBranch}`}</div>
122177
<div>创建人:{author.name}</div>
123178

179+
<h3>概览:</h3>
180+
<div dangerouslySetInnerHTML={{ __html: body }} />
181+
124182
<div className={style.btnGroup}>
125183
{showMergeBtn && (
126184
<div
127185
className={cn(style.btn, style.btnPrimary, isMerging && style.disabled)}
128186
onClick={handleMerge}
129187
>
130-
{isMerging ? '合并中...' : '合并'}
188+
{renderActionText(isMerging, '合并')}
131189
</div>
132190
)}
133191

134-
{showAllowMergeBtn && (
192+
{showAllowMergeBtn && !isAgreed && (
135193
<div
136194
className={cn(style.btn, style.btnPrimary, isAllowing && style.disabled)}
137195
onClick={handleAllowMerge}
138196
>
139-
{allowText}
197+
{renderActionText(isAllowing, '允许合并')}
198+
</div>
199+
)}
200+
201+
{showAllowMergeBtn && isAgreed && (
202+
<div
203+
className={cn(style.btn, style.btnPrimary, isDisAllowing && style.disabled)}
204+
onClick={handleDisAllowMerge}
205+
>
206+
{renderActionText(isDisAllowing, '撤销允许合并')}
140207
</div>
141208
)}
142209

@@ -145,7 +212,7 @@ const App = () => {
145212
className={cn(style.btn, isClosing && style.disabled)}
146213
onClick={handleClose}
147214
>
148-
{isClosing ? '关闭中...' : '关闭'}
215+
{renderActionText(isClosing, '关闭')}
149216
</div>
150217
)}
151218
</div>

0 commit comments

Comments
 (0)