Skip to content

Commit c255536

Browse files
committed
Merge branch 'main' of https://github.com/githru/githru-vscode-ext into view-563
2 parents bd6a7fc + a838853 commit c255536

24 files changed

+313
-184
lines changed

CONTRIBUTING.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
# Contributing to Githru
22

3-
TODO: Add support수정 필요!
3+
TODO: Add support 수정 필요!
44

55
## Develop Environment
66

77
- [node](https://nodejs.org/ko/download/) -v v14.17.3
88
- [vscode](https://code.visualstudio.com/)
9-
- [vscode plugin - eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
9+
- [vscode plugin - ESlint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
1010

1111
## Installation
1212

1313
1. 이 저장소를 [Fork](https://help.github.com/articles/fork-a-repo/) 한 후
1414
로컬 기기에 [clone](https://help.github.com/articles/cloning-a-repository/) 합니다.
15-
2. VSCode를 사용한다면 아래 과정을 통해 custom TS setting을 활성화해야합니다.
15+
2. VSCode를 사용한다면 아래 과정을 통해 custom TS setting을 활성화해야 합니다.
1616

1717
1. TypeScript 파일이 열려 있는 상태로 `ctrl(cmd) + shift + p`를 입력합니다.
1818
2. "Select TypeScript Version"을 선택합니다.
@@ -42,7 +42,7 @@ TODO: Add support수정 필요!
4242
yarn test
4343
```
4444

45-
7. [demo README](https://github.com/EveryAnalytics/react-analytics-provider/tree/main/demo)를 참고해 demo앱도 실행해 보세요.
45+
7. [demo README](https://github.com/EveryAnalytics/react-analytics-provider/tree/main/demo)를 참고해 demo 앱도 실행해 보세요.
4646

4747
## Debugging
4848

@@ -60,15 +60,15 @@ TODO: Add support수정 필요!
6060

6161
## Commit message
6262

63-
커밋 메세지는 제목과 본문을 포함해야 합니다.
63+
커밋 메시지는 제목과 본문을 포함해야 합니다.
6464

6565
제목은 해당 커밋에 대한 주요 내용을 간략하게 기록합니다.
6666
형식은 https://www.conventionalcommits.org/en/v1.0.0/ 를 따릅니다.
6767

6868
- optional scope을 사용하며, `engine`, `vscode`, `view` 3가지 scope만을 사용합니다.
6969
- ex) feat(view): Add File Icicle Tree view.
7070

71-
본문은 커밋에서 수정된 상세내역을 작성합니다. 생략 가능하며, `어떻게`보단 `무엇을`, `` 해결했는지 적어주시는 것이 좋습니다.
71+
본문은 커밋에서 수정된 상세내용을 작성합니다. 생략 가능하며, `어떻게`보단 `무엇을`, `` 해결했는지 적어주시는 것이 좋습니다.
7272

7373
상황에 따라 연관된 이슈 트래킹 번호를 포함합니다.
7474

@@ -88,7 +88,7 @@ PR의 제목 형식은 commit과 동일하게 맞추면 됩니다.
8888

8989
## Coding Guidelines
9090

91-
`vscode``eslint` 플러그인을 통해 미리 설정된 코드 컨벤션을 적용하고 검사해 볼 수 있습니다.
91+
`vscode``ESlint` 플러그인을 통해 미리 설정된 코드 컨벤션을 적용하고 검사해 볼 수 있습니다.
9292

9393
## Add yourself as a contributor
9494

README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
# githru-vscode-ext
2-
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
3-
[![All Contributors](https://img.shields.io/badge/all_contributors-23-orange.svg?style=flat-square)](#contributors-)
4-
<!-- ALL-CONTRIBUTORS-BADGE:END -->
5-
62
Lightweight but robust Githru for VSCode Extension
73

84
## Getting Started

packages/view/src/App.scss

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ body {
3232
}
3333

3434
.middle-container {
35-
display: flex;
36-
justify-content: center;
35+
display: grid;
36+
grid-template-columns: 4fr 1fr;
3737
height: calc(100vh - 200px);
38-
margin-top: 20px;
38+
39+
padding: 20px;
3940
}

packages/view/src/App.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,13 @@ import { container } from "tsyringe";
33
import { useEffect, useRef } from "react";
44
import BounceLoader from "react-spinners/BounceLoader";
55

6-
import {
7-
BranchSelector,
8-
Statistics,
9-
TemporalFilter,
10-
ThemeSelector,
11-
VerticalClusterList,
12-
FilteredAuthors,
13-
} from "components";
6+
import { BranchSelector, Statistics, TemporalFilter, ThemeSelector, VerticalClusterList } from "components";
147
import "./App.scss";
158
import type IDEPort from "ide/IDEPort";
169
import { useGlobalData } from "hooks";
1710
import { RefreshButton } from "components/RefreshButton";
1811
import type { IDESentEvents } from "types/IDESentEvents";
12+
import type { RemoteGitHubInfo } from "types/RemoteGitHubInfo";
1913

2014
const App = () => {
2115
const initRef = useRef<boolean>(false);
@@ -39,6 +33,18 @@ const App = () => {
3933
}
4034
}, [handleChangeAnalyzedData, handleChangeBranchList, ideAdapter, setLoading]);
4135

36+
const { setOwner, setRepo } = useGlobalData();
37+
useEffect(() => {
38+
const handleMessage = (event: MessageEvent<RemoteGitHubInfo>) => {
39+
const message = event.data;
40+
setOwner(message.data.owner);
41+
setRepo(message.data.repo);
42+
};
43+
44+
window.addEventListener("message", handleMessage);
45+
return () => window.removeEventListener("message", handleMessage);
46+
}, []);
47+
4248
if (loading) {
4349
return (
4450
<BounceLoader
@@ -65,7 +71,6 @@ const App = () => {
6571
</div>
6672
<div className="top-container">
6773
<TemporalFilter />
68-
<FilteredAuthors />
6974
</div>
7075
<div className="middle-container">
7176
{filteredData.length !== 0 ? (
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
.selected-container {
2-
position: relative;
3-
top: 30px;
42
display: flex;
5-
flex-wrap: wrap;
3+
align-items: center;
4+
gap: 15px;
65
width: 100%;
7-
padding: 4px 6px;
6+
}
7+
8+
.selected-content {
9+
display: flex;
10+
flex-wrap: wrap;
811
box-sizing: border-box;
12+
align-items: center;
913
}

packages/view/src/components/FilteredAuthors/FilteredAuthors.tsx

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,34 @@ const FilteredAuthors = () => {
1010
const authSrcMap = usePreLoadAuthorImg();
1111
const selectedClusters = getInitData(selectedData);
1212

13+
// 이미 선택된 사용자를 관리
14+
const addedAuthors = new Set();
15+
1316
return (
1417
<div className="selected-container">
15-
{authSrcMap &&
16-
selectedClusters.map((selectedCluster) => {
17-
return selectedCluster.summary.authorNames.map((authorArray: string[]) => {
18-
return authorArray.map((authorName: string) => (
19-
<Author
20-
key={authorName}
21-
name={authorName}
22-
src={authSrcMap[authorName]}
23-
/>
24-
));
25-
});
26-
})}
18+
{selectedClusters.length > 0 && <p>Authors:</p>}
19+
<div className="selected-content">
20+
{authSrcMap &&
21+
selectedClusters.map((selectedCluster) => {
22+
return selectedCluster.summary.authorNames.map((authorArray: string[]) => {
23+
return authorArray.map((authorName: string) => {
24+
// 이미 추가된 사용자인지 확인 후 추가되지 않은 경우에만 추가하고 Set에 이름을 저장
25+
if (!addedAuthors.has(authorName)) {
26+
addedAuthors.add(authorName);
27+
return (
28+
<Author
29+
key={authorName}
30+
name={authorName}
31+
src={authSrcMap[authorName]}
32+
/>
33+
);
34+
}
35+
// 이미 추가된 사용자인 경우 null 반환
36+
return null;
37+
});
38+
});
39+
})}
40+
</div>
2741
</div>
2842
);
2943
};

packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.scss

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44
width: fit-content;
55
display: flex;
66
flex-direction: column;
7-
align-items: flex-end;
7+
align-items: flex-start;
88
}
99

1010
.author-bar-chart__header {
1111
width: 100%;
1212
text-align: right;
13-
padding-left: 40px;
1413

1514
& .select-box {
1615
font-size: 10px;

packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.tsx

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,35 @@ import "./AuthorBarChart.scss";
1616

1717
const AuthorBarChart = () => {
1818
const { data: totalData, filteredData, setSelectedData, setFilteredData } = useGlobalData();
19-
const rawData = useGetSelectedData();
2019

20+
const rawData = useGetSelectedData();
2121
const svgRef = useRef<SVGSVGElement>(null);
2222
const tooltipRef = useRef<HTMLDivElement>(null);
2323

2424
const [metric, setMetric] = useState<MetricType>(METRIC_TYPE[0]);
25-
const [prevData, setPrevData] = useState<ClusterNode[]>([]);
26-
25+
const [prevData, setPrevData] = useState<ClusterNode[][]>([]);
26+
const [selectedAuthor, setSelectedAuthor] = useState<string>("");
2727
const authorData = getDataByAuthor(rawData as ClusterNode[]);
28+
2829
let data = authorData.sort((a, b) => {
2930
if (a[metric] === b[metric]) {
3031
return sortDataByName(a.name, b.name);
3132
}
3233
return b[metric] - a[metric];
3334
});
35+
3436
if (data.length > 10) {
35-
data = data.slice(0, 10);
37+
const topAuthors = data.slice(0, 9);
38+
const otherAuthors = data.slice(9);
39+
const reducedOtherAuthors = otherAuthors.reduce(
40+
(acc, cur) => {
41+
acc[metric] += cur[metric];
42+
acc.names?.push(cur.name);
43+
return acc;
44+
},
45+
{ name: "others", commit: 0, insertion: 0, deletion: 0, names: [] } as AuthorDataType
46+
);
47+
data = [...topAuthors, reducedOtherAuthors];
3648
}
3749

3850
useEffect(() => {
@@ -78,13 +90,11 @@ const AuthorBarChart = () => {
7890
.text(`${metric} # / Total ${metric} # (%)`);
7991

8092
// Event handler
81-
const handleMouseOver = () => {
82-
tooltip.style("display", "inline-block");
83-
};
84-
const handleMouseMove = (e: MouseEvent<SVGRectElement | SVGTextElement>, d: AuthorDataType) => {
93+
const handleMouseOver = (e: MouseEvent<SVGRectElement | SVGTextElement>, d: AuthorDataType) => {
8594
tooltip
95+
.style("display", "inline-block")
8696
.style("left", `${e.pageX - 70}px`)
87-
.style("top", `${e.pageY - 70}px`)
97+
.style("top", `${e.pageY - 120}px`)
8898
.html(
8999
`<p class="name">${d.name}</p>
90100
<p>${metric}:
@@ -96,20 +106,46 @@ const AuthorBarChart = () => {
96106
</p>`
97107
);
98108
};
109+
110+
const handleMouseMove = (e: MouseEvent<SVGRectElement | SVGTextElement>) => {
111+
tooltip.style("left", `${e.pageX - 70}px`).style("top", `${e.pageY - 120}px`);
112+
};
99113
const handleMouseOut = () => {
100114
tooltip.style("display", "none");
101115
};
116+
102117
const handleClickBar = (_: MouseEvent<SVGRectElement | SVGTextElement>, d: AuthorDataType) => {
103-
const isAuthorSelected = !!prevData.length;
118+
const isAuthorSelected = selectedAuthor === d.name && d.name !== "others";
119+
120+
const getNewFilteredData = (names: string[]) => {
121+
return sortDataByAuthor(totalData, names);
122+
};
104123

105124
if (isAuthorSelected) {
106-
setFilteredData(prevData);
107-
setPrevData([]);
108-
} else {
109-
setFilteredData(sortDataByAuthor(filteredData, d.name));
110-
setPrevData(filteredData);
125+
// 현재 선택된 사용자를 다시 클릭하면 이전 데이터로 복원
126+
const newFilteredData = prevData.length > 0 ? prevData.pop() : filteredData;
127+
setFilteredData(newFilteredData ?? filteredData);
128+
setPrevData([...prevData]);
129+
setSelectedAuthor("");
130+
setSelectedData([]);
131+
tooltip.style("display", "none");
132+
return;
133+
}
134+
135+
if (d.name === "others") {
136+
// "others" 바를 클릭할 때
137+
setPrevData([...prevData, filteredData]);
138+
setFilteredData(getNewFilteredData(d.names || []));
139+
setSelectedAuthor(d.name);
140+
setSelectedData([]);
141+
tooltip.style("display", "none");
142+
return;
111143
}
112144

145+
// 특정 사용자를 클릭할 때
146+
setPrevData([...prevData, filteredData]);
147+
setFilteredData(getNewFilteredData([d.name]));
148+
setSelectedAuthor(d.name);
113149
setSelectedData([]);
114150
tooltip.style("display", "none");
115151
};
@@ -158,14 +194,26 @@ const AuthorBarChart = () => {
158194
.attr("width", 14)
159195
.attr("height", 14);
160196
});
161-
}, [data, filteredData, metric, prevData, rawData, setFilteredData, setSelectedData, totalData]);
197+
}, [
198+
data,
199+
filteredData,
200+
metric,
201+
prevData,
202+
rawData,
203+
setFilteredData,
204+
setSelectedData,
205+
totalData,
206+
selectedAuthor,
207+
setSelectedAuthor,
208+
]);
162209

163210
const handleChangeMetric = (e: ChangeEvent<HTMLSelectElement>): void => {
164211
setMetric(e.target.value as MetricType);
165212
};
166213

167214
return (
168215
<div className="author-bar-chart__container">
216+
<p>Author Bar Chart</p>
169217
<div className="author-bar-chart__header">
170218
<select
171219
className="select-box"

packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.type.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export type AuthorDataType = {
55
commit: number;
66
insertion: number;
77
deletion: number;
8+
names?: string[];
89
};
910

1011
export type MetricType = (typeof METRIC_TYPE)[number];

packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.util.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,16 @@ export const convertNumberFormat = (d: number | { valueOf(): number }): string =
5151
return d3.format("~s")(d);
5252
};
5353

54-
export const sortDataByAuthor = (data: ClusterNode[], author: string): ClusterNode[] => {
54+
export const sortDataByAuthor = (data: ClusterNode[], names: string[]): ClusterNode[] => {
5555
return data.reduce((acc: ClusterNode[], cluster: ClusterNode) => {
5656
const checkedCluster = cluster.commitNodeList.filter((commitNode: CommitNode) =>
57-
commitNode.commit.author.names.includes(author)
57+
names.some((name) => commitNode.commit.author.names.includes(name))
5858
);
59-
if (!checkedCluster.length) return acc;
60-
return [...acc, { nodeTypeName: "CLUSTER" as const, commitNodeList: checkedCluster }];
59+
60+
if (checkedCluster.length > 0) {
61+
acc.push({ nodeTypeName: "CLUSTER", commitNodeList: checkedCluster });
62+
}
63+
64+
return acc;
6165
}, []);
6266
};
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
@import "styles/_pallete";
22

33
.file-icicle-summary {
4+
width: 90%;
45
text {
5-
fill: $white;
6+
fill: var(--primary-color);
7+
filter: invert(100) grayscale(100) contrast(100);
68
}
79
}

packages/view/src/components/Statistics/FileIcicleSummary/FileIcicleSummary.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ const FileIcicleSummary = () => {
137137

138138
return (
139139
<div className="file-icicle-summary">
140+
<p>File Summary</p>
140141
<svg ref={$summary} />
141142
</div>
142143
);

0 commit comments

Comments
 (0)