diff --git a/INSTALLATION.md b/INSTALLATION.md
index 9b894d44..b2c0820d 100644
--- a/INSTALLATION.md
+++ b/INSTALLATION.md
@@ -8,7 +8,7 @@ We welcome users to install and try our Hypertrons Chrome Extension. If you have
### Chrome / Edge
-1. Install our extension from [Chrome Web Store](https://chrome.google.com/webstore/detail/hypercrx/ijchfbpdgeljmhnhokmekkecpbdkgabc) or [Edge Extensions Home](https://microsoftedge.microsoft.com/addons/detail/hypercrx/lbbajaehiibofpconjgdjonmkidpcome?hl=zh-CN)
+1. Install our extension from [Chrome Web Store](https://chrome.google.com/webstore/detail/hypercrx/ijchfbpdgeljmhnhokmekkecpbdkgabc) or [Edge Extensions Home](https://microsoftedge.microsoft.com/addons/detail/hypercrx/lbbajaehiibofpconjgdjonmkidpcome?hl=zh-CN)
[Chrome] Visit [Chrome Web Store](https://chrome.google.com/webstore/detail/hypercrx/ijchfbpdgeljmhnhokmekkecpbdkgabc),and click **[Add to Chrome]**.
@@ -46,7 +46,6 @@ We welcome users to install and try our Hypertrons Chrome Extension. If you have
-
3. You can change configuration of hypertrons-crx by visiting the setting page:
@@ -74,7 +73,6 @@ We welcome users to install and try our Hypertrons Chrome Extension. If you have
-
## Install from released package
The latest release can be found on this page https://github.com/hypertrons/hypertrons-crx/releases , where `hypertrons.crx` and `hypertrons.zip` are available under `Assets`. Currently, the extension can be installed to multiple browsers that use the chromium kernel. Some common ones are listed as follows:
diff --git a/INSTALLATION.zh-CN.md b/INSTALLATION.zh-CN.md
index c34075c1..0ba8f78f 100644
--- a/INSTALLATION.zh-CN.md
+++ b/INSTALLATION.zh-CN.md
@@ -12,7 +12,7 @@ Hypertrons-crx 现已登陆 [Chrome 网上应用商店](https://chrome.google.co
### Chrome 浏览器 / Edge 浏览器
-1. 安装Hypertrons-crx插件
+1. 安装 Hypertrons-crx 插件
[Chrome]点击 [此链接](https://chrome.google.com/webstore/detail/hypercrx/ijchfbpdgeljmhnhokmekkecpbdkgabc) 访问 “Hypercrx” 扩展程序主页,点击 “添加至 Chrome” ,并确认。
@@ -50,9 +50,6 @@ Hypertrons-crx 现已登陆 [Chrome 网上应用商店](https://chrome.google.co
-
-
-
3. 如需对插件进行设置,请点击以下按钮,可以进入到插件设置页面。在设置页面,您可以更改相关配置。
-
## 手动下载安装包
您可以在[这里](https://github.com/hypertrons/hypertrons-crx/releases)下载最新的安装包。 当前版本支持 Chromium 内核浏览器,如:
diff --git a/README.md b/README.md
index b265ad55..b585caa4 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,6 @@ You can find these dashboards in:
-
### Project Related
@@ -93,8 +92,7 @@ You can find these dashboards in:
-
-
+
- **Project Correlation Network**: Project Correlation Network shows the correlation between projects for a given time period. From this graph you can find the projects that are related to the given project.
@@ -104,7 +102,6 @@ You can find these dashboards in:
- **Repo Details**: Trends of Activity, OpenRank, Participants, Fork Events, Star Events, Open Issue Events, Issue Comment Events, Open PR Events, PR Merge Events, Review Comment Events, Merged Addition & Deletion Code Lines.
-
### Developer Related
@@ -133,9 +130,7 @@ You can find these dashboards in:
/>
-
-
-
+
- **Developer Collaboration Network**: Developer Collaboration Network shows the collaboration between developers for a given time period. From this graph you can find other developers who are closest to a given developer.
- **Developer's Most Participated Repos**: Developer's Most Participated Repos shows the active projects of developers in a given time period. From this graph you can find out the most active repositories for a given developer.
@@ -151,6 +146,7 @@ OSS-GPT is an open source project document answering robot integrated with [Docs
## Contributing
Please read [CONTRIBUTING](./CONTRIBUTING.md) if you are new here or not familiar with the basic rules of Git/GitHub world.
+
### Requirements
1. node >= 18
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 004ec0a8..dabdc09e 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -16,15 +16,15 @@ Language : [English](./README.md) | 中文
## 安装与使用 📢
- [前往Chrome商店安装插件](https://chrome.google.com/webstore/detail/hypercrx/ijchfbpdgeljmhnhokmekkecpbdkgabc)
+ [前往 Chrome 商店安装插件](https://chrome.google.com/webstore/detail/hypercrx/ijchfbpdgeljmhnhokmekkecpbdkgabc)
- [前往Edge商店安装插件](https://microsoftedge.microsoft.com/addons/detail/hypercrx/lbbajaehiibofpconjgdjonmkidpcome)
+ [前往 Edge 商店安装插件](https://microsoftedge.microsoft.com/addons/detail/hypercrx/lbbajaehiibofpconjgdjonmkidpcome)
获取更多信息,请查阅[安装指南](./INSTALLATION.zh-CN.md)。
## 数据来源
-`Hypercrx`呈现的所有数据都由[OpenDigger](https://github.com/X-lab2017/open-digger)产生。OpenDigger是一个聚焦于开源分析的开源项目。数据会在每个月第二天更新。
+`Hypercrx`呈现的所有数据都由[OpenDigger](https://github.com/X-lab2017/open-digger)产生。OpenDigger 是一个聚焦于开源分析的开源项目。数据会在每个月第二天更新。
## 可视化看板 🔥🔥🔥
@@ -94,16 +94,15 @@ Language : [English](./README.md) | 中文
-
-
+
- **项目关系网络图**: 项目关系网络图展示了在给定的时间段内,项目与项目之间的联结关系,**_用于项目间关系的追踪与挖掘_**。从该网络图中,可以找出与该项目有联结关系的其他项目。
- **项目活跃开发者协作网络图**: 项目活跃开发者协作网络图展示了在给定的时间段内,项目内部活跃的开发者之间的协作关系,**_用于项目内部开发者关系的追踪与挖掘_**。从该网络图中,可以找出该项目中最活跃的开发者,及开发者之间的协作关系。
-- **项目活跃度&OpenRank趋势图**:项目活跃度和OpenRank趋势图显示了项目成立至今的活跃度和OpenRank这两个指标的变化。您可以利用鼠标或触控板在图表内缩放和拖拽,此外,您还可以点击Legend按钮来控制图例的显示和隐藏。
+- **项目活跃度&OpenRank 趋势图**:项目活跃度和 OpenRank 趋势图显示了项目成立至今的活跃度和 OpenRank 这两个指标的变化。您可以利用鼠标或触控板在图表内缩放和拖拽,此外,您还可以点击 Legend 按钮来控制图例的显示和隐藏。
-- **仓库详情**: 显示了以下统计指标的历史值。活跃度、OpenRank、参与人数、Fork事件、Star事件、Issue创建事件、Issue评论事件、PR创建事件、PR合入事件、Review评论事件、通过PR合入增加和删除的代码行数。
+- **仓库详情**: 显示了以下统计指标的历史值。活跃度、OpenRank、参与人数、Fork 事件、Star 事件、Issue 创建事件、Issue 评论事件、PR 创建事件、PR 合入事件、Review 评论事件、通过 PR 合入增加和删除的代码行数。
### 开发者关系挖掘
@@ -133,17 +132,15 @@ Language : [English](./README.md) | 中文
/>
-
-
+
-
-- **开发者协作网络图**: 开发者协作网络图展示了在给定的时间段内,开发者与开发者之间的协作关系, ***用于开发者关系的追踪与挖掘***。从该网络图中,可以找出与指定开发者联系较为紧密的其他开发者。
-- **活跃仓库网络图**: 活跃仓库网络图展示了在给定的时间段内,开发者的活跃项目,***用于开发者行为的追踪与挖掘***。从该网络图中,可以找出该开发者在哪些项目中活跃。
-- **开发者活跃度&OpenRank趋势图**:开发者活跃度和OpenRank趋势图显示了项目成立至今的活跃度和OpenRank这两个指标的变化。您可以利用鼠标或触控板在图表内缩放和拖拽,此外,您还可以点击Legend按钮来控制图例的显示和隐藏。
+- **开发者协作网络图**: 开发者协作网络图展示了在给定的时间段内,开发者与开发者之间的协作关系, **_用于开发者关系的追踪与挖掘_**。从该网络图中,可以找出与指定开发者联系较为紧密的其他开发者。
+- **活跃仓库网络图**: 活跃仓库网络图展示了在给定的时间段内,开发者的活跃项目,**_用于开发者行为的追踪与挖掘_**。从该网络图中,可以找出该开发者在哪些项目中活跃。
+- **开发者活跃度&OpenRank 趋势图**:开发者活跃度和 OpenRank 趋势图显示了项目成立至今的活跃度和 OpenRank 这两个指标的变化。您可以利用鼠标或触控板在图表内缩放和拖拽,此外,您还可以点击 Legend 按钮来控制图例的显示和隐藏。
## OSS-GPT
-OSS-GPT是集成了[DocsGPT](https://github.com/arc53/docsgpt)能力的开源项目文档问答机器人。对已经支持的项目,只需打开对应的项目仓库主页,即可询问有关于该仓库的任何问题。你可以通过[issue#609](https://github.com/hypertrons/hypertrons-crx/issues/609)以使你的开源项目获得OSS-GPT的支持。
+OSS-GPT 是集成了[DocsGPT](https://github.com/arc53/docsgpt)能力的开源项目文档问答机器人。对已经支持的项目,只需打开对应的项目仓库主页,即可询问有关于该仓库的任何问题。你可以通过[issue#609](https://github.com/hypertrons/hypertrons-crx/issues/609)以使你的开源项目获得 OSS-GPT 的支持。
= 16.14
2. yarn
+
### 快速开始
1. git clone https://github.com/hypertrons/hypertrons-crx
diff --git a/package.json b/package.json
index f2e2dff4..c08a7286 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"update-version": "node scripts/bump-version.cjs"
},
"dependencies": {
+ "@ant-design/icons": "^5.2.6",
"@fluentui/react": "^8.62.0",
"@fluentui/react-hooks": "^8.3.13",
"@hot-loader/react-dom": "^17.0.2",
diff --git a/scripts/bump-version.cjs b/scripts/bump-version.cjs
index c28ced5d..89200f31 100644
--- a/scripts/bump-version.cjs
+++ b/scripts/bump-version.cjs
@@ -1,14 +1,15 @@
// according to https://github.com/TriPSs/conventional-changelog-action#pre-commit-hook
// this script should be a CommonJS module
-const semver = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i
+const semver =
+ /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i;
-function validate({version}) {
+function validate({ version }) {
// validate if the given version conforms semver
return String(version).match(semver) === null;
}
-function compare({oldVersion, newVersion}) {
+function compare({ oldVersion, newVersion }) {
// compare oldVersion and newVersion number
// return -1 if oldVersion is greater;
// 0 if two versions are equal;
@@ -32,8 +33,10 @@ async function bump({ version, deploy }) {
// update package.json
const pkgPath = 'package.json';
const pkg = await readJson(pkgPath);
- if (compare({oldVersion: pkg.version, newVersion: version}) <= 0) {
- throw new Error('Input version number is not greater than the current version number!');
+ if (compare({ oldVersion: pkg.version, newVersion: version }) <= 0) {
+ throw new Error(
+ 'Input version number is not greater than the current version number!'
+ );
}
pkg.version = version;
writeJson(pkgPath, pkg);
@@ -46,8 +49,15 @@ async function bump({ version, deploy }) {
update_info.chrome.latest_version = version;
update_info.edge.latest_version = version;
}
- if (compare({oldVersion: update_info.develop.latest_version, newVersion: version}) <= 0) {
- throw new Error('Input version number is not greater than the current version number!');
+ if (
+ compare({
+ oldVersion: update_info.develop.latest_version,
+ newVersion: version,
+ }) <= 0
+ ) {
+ throw new Error(
+ 'Input version number is not greater than the current version number!'
+ );
}
update_info.develop.latest_version = version;
writeJson(infoPath, update_info);
@@ -58,7 +68,7 @@ module.exports = { bump };
try {
const [nodePath, scriptPath, versionNumber, ...otherArgs] = process.argv;
if (versionNumber !== undefined) {
- if (validate({version: versionNumber})) {
+ if (validate({ version: versionNumber })) {
// version number is not valid
throw new Error('Input version number is valid');
}
@@ -69,4 +79,3 @@ try {
return -1;
}
return 0;
-
diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json
index 81136a96..ca230938 100644
--- a/src/locales/en/messages.json
+++ b/src/locales/en/messages.json
@@ -155,9 +155,6 @@
"component_projectCorrelationNetwork_title": {
"message": "Project Correlation Network"
},
- "component_projectRacingBar_ReplayButton": {
- "message": "Replay"
- },
"component_projectRacingBar_description": {
"message": "This chart shows how the activity values of contributors in this repository evolve."
},
diff --git a/src/locales/zh_CN/messages.json b/src/locales/zh_CN/messages.json
index 95a2e690..de70b5f6 100644
--- a/src/locales/zh_CN/messages.json
+++ b/src/locales/zh_CN/messages.json
@@ -155,9 +155,6 @@
"component_projectCorrelationNetwork_title": {
"message": "项目关系网络图"
},
- "component_projectRacingBar_ReplayButton": {
- "message": "重播"
- },
"component_projectRacingBar_description": {
"message": "贡献者活跃度滚榜展示了项目贡献者的活跃度演化过程。"
},
diff --git a/src/pages/ContentScripts/features/repo-activity-racing-bar/AvatarColorStore.ts b/src/pages/ContentScripts/features/repo-activity-racing-bar/AvatarColorStore.ts
index b7c5401f..bcaf16d0 100644
--- a/src/pages/ContentScripts/features/repo-activity-racing-bar/AvatarColorStore.ts
+++ b/src/pages/ContentScripts/features/repo-activity-racing-bar/AvatarColorStore.ts
@@ -56,6 +56,13 @@ class AvatarColorStore {
(rgb: RGB) => `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`
);
});
+ // Store the updated cache entry with the unique key.
+ await chrome.storage.local.set({
+ [cacheKey]: {
+ colors,
+ lastUpdated: now,
+ },
+ });
} catch (error) {
console.error(
`Cannot extract colors of the avatar of ${loginId}, error info: `,
@@ -64,14 +71,6 @@ class AvatarColorStore {
colors = Array(COLOR_COUNT).fill('rgb(255, 255, 255)');
}
- // Store the updated cache entry with the unique key.
- await chrome.storage.local.set({
- [cacheKey]: {
- colors,
- lastUpdated: now,
- },
- });
-
return colors;
}
diff --git a/src/pages/ContentScripts/features/repo-activity-racing-bar/PlayerButton.tsx b/src/pages/ContentScripts/features/repo-activity-racing-bar/PlayerButton.tsx
new file mode 100644
index 00000000..12411548
--- /dev/null
+++ b/src/pages/ContentScripts/features/repo-activity-racing-bar/PlayerButton.tsx
@@ -0,0 +1,58 @@
+import React, { useRef } from 'react';
+import { Button, Tooltip } from 'antd';
+
+interface PlayerButtonProps {
+ tooltip?: string;
+ icon: JSX.Element;
+ onClick?: () => void;
+ onLongPress?: () => void;
+}
+
+export const PlayerButton = ({
+ tooltip,
+ icon,
+ onClick,
+ onLongPress,
+}: PlayerButtonProps): JSX.Element => {
+ const pressingRef = useRef(false);
+ const longPressDetectedRef = useRef(false);
+ const timerRef = useRef();
+
+ const handleMouseDown = () => {
+ pressingRef.current = true;
+ timerRef.current = setTimeout(() => {
+ if (pressingRef.current) {
+ longPressDetectedRef.current = true;
+ onLongPress?.();
+ }
+ }, 1000);
+ };
+
+ const handleMouseUp = () => {
+ clearTimeout(timerRef.current!);
+ pressingRef.current = false;
+
+ if (longPressDetectedRef.current) {
+ longPressDetectedRef.current = false;
+ return;
+ }
+
+ onClick?.();
+ };
+
+ return (
+
+
+
+ );
+};
diff --git a/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx b/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx
index 6f48672b..0de01a0b 100644
--- a/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx
+++ b/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx
@@ -1,239 +1,178 @@
-import { RepoActivityDetails } from '.';
-import { avatarColorStore } from './AvatarColorStore';
-
-import React, { useEffect, useState, useRef } from 'react';
-import * as echarts from 'echarts';
-import type { EChartsOption, EChartsType, BarSeriesOption } from 'echarts';
+import {
+ RepoActivityDetails,
+ getOption,
+ countLongTermContributors,
+ DEFAULT_FREQUENCY,
+} from './data';
+import { useLoadedAvatars } from './useLoadedAvatars';
+import sleep from '../../../../helpers/sleep';
+
+import React, {
+ useEffect,
+ useState,
+ useRef,
+ forwardRef,
+ useImperativeHandle,
+ ForwardedRef,
+} from 'react';
import { Spin } from 'antd';
+import * as echarts from 'echarts';
+import type { EChartsType } from 'echarts';
+
+export interface MediaControlers {
+ play: () => void;
+ pause: () => void;
+ next: () => void;
+ previous: () => void;
+ latest: () => void;
+ earliest: () => void;
+}
interface RacingBarProps {
- repoName: string;
+ speed: number;
data: RepoActivityDetails;
+ setPlaying: (playing: boolean) => void;
}
-const updateFrequency = 3000;
-
-const option: EChartsOption = {
- grid: {
- top: 10,
- bottom: 30,
- left: 160,
- right: 50,
- },
- xAxis: {
- max: 'dataMax',
- },
- yAxis: {
- type: 'category',
- inverse: true,
- max: 10,
- axisLabel: {
- show: true,
- fontSize: 14,
- formatter: function (value: string) {
- if (!value || value.endsWith('[bot]')) return value;
- return `${value} {avatar${value.replaceAll('-', '')}|}`;
- },
- },
- axisTick: {
- show: false,
- },
- animationDuration: 0,
- animationDurationUpdate: 200,
- },
- series: [
- {
- realtimeSort: true,
- seriesLayoutBy: 'column',
- type: 'bar',
- data: undefined,
- encode: {
- x: 1,
- y: 0,
- },
- label: {
- show: true,
- precision: 1,
- position: 'right',
- valueAnimation: true,
- fontFamily: 'monospace',
- },
- },
- ],
- // Disable init animation.
- animationDuration: 0,
- animationDurationUpdate: updateFrequency,
- animationEasing: 'linear',
- animationEasingUpdate: 'linear',
- graphic: {
- elements: [
- {
- type: 'text',
- right: 60,
- bottom: 60,
- style: {
- text: undefined,
- font: 'bolder 60px monospace',
- fill: 'rgba(100, 100, 100, 0.25)',
- },
- z: 100,
- },
- ],
- },
-};
-
-const updateMonth = async (
- instance: EChartsType,
- data: RepoActivityDetails,
- month: string
-) => {
- const rich: any = {};
- const barData: BarSeriesOption['data'] = await Promise.all(
- data[month].map(async (item) => {
- // rich name cannot contain special characters such as '-'
- rich[`avatar${item[0].replaceAll('-', '')}`] = {
- backgroundColor: {
- image: `https://avatars.githubusercontent.com/${item[0]}?s=48&v=4`,
- },
- height: 20,
- };
- const avatarColors = await avatarColorStore.getColors(item[0]);
- return {
- value: item,
- itemStyle: {
- color: {
- type: 'linear',
- x: 0,
- y: 0,
- x2: 1,
- y2: 0,
- colorStops: [
- {
- offset: 0,
- color: avatarColors[0],
- },
- {
- offset: 0.5,
- color: avatarColors[1],
- },
- ],
- global: false,
- },
- },
+const RacingBar = forwardRef(
+ (
+ { speed, data, setPlaying }: RacingBarProps,
+ forwardedRef: ForwardedRef
+ ): JSX.Element => {
+ const divEL = useRef(null);
+ const timerRef = useRef();
+ const speedRef = useRef(speed);
+ speedRef.current = speed;
+
+ const months = Object.keys(data);
+ const monthIndexRef = useRef(months.length - 1);
+
+ const [longTermContributorsCount, contributors] =
+ countLongTermContributors(data);
+ const maxBars = longTermContributorsCount >= 20 ? 20 : 10;
+ const height = longTermContributorsCount >= 20 ? 600 : 300;
+ const [loadedAvatars, loadAvatars] = useLoadedAvatars(contributors);
+
+ const updateMonth = async (
+ instance: EChartsType,
+ month: string,
+ enableAnimation: boolean
+ ) => {
+ const option = await getOption(
+ data,
+ month,
+ speedRef.current,
+ maxBars,
+ enableAnimation
+ );
+ instance.setOption(option);
+ };
+
+ const play = async () => {
+ const nextMonth = async () => {
+ monthIndexRef.current++;
+ const instance = echarts.getInstanceByDom(divEL.current!)!;
+ updateMonth(instance, months[monthIndexRef.current], true);
+ if (monthIndexRef.current < months.length - 1) {
+ timerRef.current = setTimeout(
+ nextMonth,
+ DEFAULT_FREQUENCY / speedRef.current
+ );
+ } else {
+ setTimeout(() => {
+ setPlaying(false);
+ }, DEFAULT_FREQUENCY / speedRef.current);
+ }
};
- })
- );
- // @ts-ignore
- option.yAxis.axisLabel.rich = rich;
- // @ts-ignore
- option.series[0].data = barData;
- // @ts-ignore
- option.graphic.elements[0].style.text = month;
-
- // it seems that hidden bars are also rendered, so when each setOption merge more data into the chart,
- // the fps goes down. So we use notMerge to avoid merging data. But this disables the xAxis animation.
- // Hope we can find a better solution.
- instance.setOption(option, {
- notMerge: true,
- });
-};
-
-let timer: NodeJS.Timeout;
-
-const play = (instance: EChartsType, data: RepoActivityDetails) => {
- const months = Object.keys(data);
- let i = 0;
-
- const playNext = async () => {
- await updateMonth(instance, data, months[i]);
- i++;
- if (i < months.length) {
- timer = setTimeout(playNext, updateFrequency);
- }
- };
-
- playNext();
-};
-
-/**
- * Count the number of unique contributors in the data
- * @returns [number of long term contributors, contributors' names]
- */
-const countLongTermContributors = (
- data: RepoActivityDetails
-): [number, string[]] => {
- const contributors = new Map();
- Object.keys(data).forEach((month) => {
- data[month].forEach((item) => {
- if (contributors.has(item[0])) {
- contributors.set(item[0], contributors.get(item[0])! + 1);
- } else {
- contributors.set(item[0], 0);
- }
- });
- });
- let count = 0;
- contributors.forEach((value) => {
- // only count contributors who have contributed more than 3 months
- if (value >= 3) {
- count++;
- }
- });
- return [count, [...contributors.keys()]];
-};
-
-const RacingBar = ({ data }: RacingBarProps): JSX.Element => {
- const [loadedAvatars, setLoadedAvatars] = useState(0);
- const divEL = useRef(null);
-
- let height = 300;
- const [longTermContributorsCount, contributors] =
- countLongTermContributors(data);
- if (longTermContributorsCount >= 20) {
- // @ts-ignore
- option.yAxis.max = 20;
- height = 600;
- }
- useEffect(() => {
- (async () => {
- if (!divEL.current) return;
-
- const chartDOM = divEL.current;
- const instance = echarts.init(chartDOM);
+ setPlaying(true);
+ // if the current month is the latest month, go to the beginning
+ if (monthIndexRef.current === months.length - 1) {
+ earliest();
+ await sleep(DEFAULT_FREQUENCY / speedRef.current);
+ }
+ nextMonth();
+ };
- // load avatars and extract colors before playing the chart
- const promises = contributors.map(async (contributor) => {
- await avatarColorStore.getColors(contributor);
- setLoadedAvatars((loadedAvatars) => loadedAvatars + 1);
- });
- await Promise.all(promises);
+ const pause = () => {
+ setPlaying(false);
- play(instance, data);
+ if (timerRef.current) {
+ clearTimeout(timerRef.current);
+ }
+ };
+
+ const next = () => {
+ pause();
+ if (monthIndexRef.current < months.length - 1) {
+ const instance = echarts.getInstanceByDom(divEL.current!)!;
+ monthIndexRef.current++;
+ updateMonth(instance, months[monthIndexRef.current], false);
+ }
+ };
+
+ const previous = () => {
+ pause();
+ if (monthIndexRef.current > 0) {
+ const instance = echarts.getInstanceByDom(divEL.current!)!;
+ monthIndexRef.current--;
+ updateMonth(instance, months[monthIndexRef.current], false);
+ }
+ };
+
+ const latest = () => {
+ pause();
+ const instance = echarts.getInstanceByDom(divEL.current!)!;
+ monthIndexRef.current = months.length - 1;
+ updateMonth(instance, months[monthIndexRef.current], false);
+ };
+
+ const earliest = () => {
+ const instance = echarts.getInstanceByDom(divEL.current!)!;
+ monthIndexRef.current = 0;
+ updateMonth(instance, months[monthIndexRef.current], false);
+ };
+
+ // expose startRecording and stopRecording to parent component
+ useImperativeHandle(forwardedRef, () => ({
+ play,
+ pause,
+ next,
+ previous,
+ latest,
+ earliest,
+ }));
+
+ useEffect(() => {
+ (async () => {
+ await loadAvatars();
+ const instance = echarts.init(divEL.current!);
+ updateMonth(instance, months[monthIndexRef.current], false);
+ })();
return () => {
- if (!instance.isDisposed()) {
- instance.dispose();
+ if (timerRef.current) {
+ clearTimeout(timerRef.current);
}
- // clear timer if user replay the chart before it finishes
- if (timer) {
- clearTimeout(timer);
+ const instance = echarts.getInstanceByDom(divEL.current!);
+ if (instance && !instance.isDisposed()) {
+ instance.dispose();
}
};
- })();
- }, []);
-
- return (
-