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. 如需对插件进行设置,请点击以下按钮,可以进入到插件设置页面。在设置页面,您可以更改相关配置。 @@ -80,7 +77,6 @@ Hypertrons-crx 现已登陆 [Chrome 网上应用商店](https://chrome.google.co
- ## 手动下载安装包 您可以在[这里](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 [前往Chrome商店安装插件](https://chrome.google.com/webstore/detail/hypercrx/ijchfbpdgeljmhnhokmekkecpbdkgabc) +Chrome [前往 Chrome 商店安装插件](https://chrome.google.com/webstore/detail/hypercrx/ijchfbpdgeljmhnhokmekkecpbdkgabc) -Edge [前往Edge商店安装插件](https://microsoftedge.microsoft.com/addons/detail/hypercrx/lbbajaehiibofpconjgdjonmkidpcome) +Edge [前往 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/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/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx b/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx index 6f48672b..993674d8 100644 --- a/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx +++ b/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx @@ -1,10 +1,23 @@ import { RepoActivityDetails } from '.'; import { avatarColorStore } from './AvatarColorStore'; -import React, { useEffect, useState, useRef } from 'react'; +import React, { + useEffect, + useState, + useRef, + forwardRef, + useImperativeHandle, + ForwardedRef, +} from 'react'; import * as echarts from 'echarts'; -import type { EChartsOption, EChartsType, BarSeriesOption } from 'echarts'; import { Spin } from 'antd'; +import type { BarSeriesOption, EChartsOption, EChartsType } from 'echarts'; + +export interface MediaControlers { + play: () => void; + startRecording: () => void; + stopRecording: () => void; +} interface RacingBarProps { repoName: string; @@ -140,7 +153,7 @@ const updateMonth = async ( let timer: NodeJS.Timeout; -const play = (instance: EChartsType, data: RepoActivityDetails) => { +const playFromStart = (instance: EChartsType, data: RepoActivityDetails) => { const months = Object.keys(data); let i = 0; @@ -182,58 +195,121 @@ const countLongTermContributors = ( 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; - } +const RacingBar = forwardRef( + ( + { data }: RacingBarProps, + forwardedRef: ForwardedRef + ): JSX.Element => { + const [loadedAvatars, setLoadedAvatars] = useState(0); + const divEL = useRef(null); + const mediaRecorderRef = useRef(null); + const chunksRef = useRef([]); + + 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; + + // 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); - useEffect(() => { - (async () => { + play(); + })(); + }, []); + + const play = () => { if (!divEL.current) return; + // clear timer if user replay the chart before it finishes + if (timer) { + clearTimeout(timer); + } + let instance = echarts.getInstanceByDom(divEL.current)!; + if (instance && !instance.isDisposed()) { + instance.dispose(); + } const chartDOM = divEL.current; - const instance = echarts.init(chartDOM); + instance = echarts.init(chartDOM); + playFromStart(instance, data); + }; - // load avatars and extract colors before playing the chart - const promises = contributors.map(async (contributor) => { - await avatarColorStore.getColors(contributor); - setLoadedAvatars((loadedAvatars) => loadedAvatars + 1); + const startRecording = () => { + if (!divEL.current) return; + + console.log('start recording'); + // Start the media recorder + const canvas: HTMLCanvasElement = + divEL.current.querySelector('div > canvas')!; + const stream = canvas.captureStream(60); + mediaRecorderRef.current = new MediaRecorder(stream, { + mimeType: 'video/mp4', }); - await Promise.all(promises); - - play(instance, data); - - return () => { - if (!instance.isDisposed()) { - instance.dispose(); - } - // clear timer if user replay the chart before it finishes - if (timer) { - clearTimeout(timer); - } + + mediaRecorderRef.current.ondataavailable = function (event) { + chunksRef.current.push(event.data); }; - })(); - }, []); - - return ( -
- -
- -
- ); -}; + + // Start recording + mediaRecorderRef.current.start(); + }; + + const stopRecording = () => { + if (!mediaRecorderRef.current) return; + + console.log('stop recording'); + // Handle the stop event + mediaRecorderRef.current.onstop = function () { + const blob = new Blob(chunksRef.current, { type: 'video/mp4' }); + const url = URL.createObjectURL(blob); + + // Create a video element and set the source to the recorded video + const video = document.createElement('video'); + video.src = url; + + // Download the video + const a = document.createElement('a'); + a.download = 'chart_animation.mp4'; + a.href = url; + a.click(); + + // Clean up + URL.revokeObjectURL(url); + chunksRef.current = []; + }; + mediaRecorderRef.current.stop(); + }; + + // expose startRecording and stopRecording to parent component + useImperativeHandle(forwardedRef, () => ({ + play, + startRecording, + stopRecording, + })); + + return ( +
+ +
+ +
+ ); + } +); export default RacingBar; diff --git a/src/pages/ContentScripts/features/repo-activity-racing-bar/view.tsx b/src/pages/ContentScripts/features/repo-activity-racing-bar/view.tsx index 4aad56b3..252b0b4c 100644 --- a/src/pages/ContentScripts/features/repo-activity-racing-bar/view.tsx +++ b/src/pages/ContentScripts/features/repo-activity-racing-bar/view.tsx @@ -1,11 +1,11 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import getMessageByLocale from '../../../../helpers/get-message-by-locale'; import optionsStorage, { HypercrxOptions, defaults, } from '../../../../options-storage'; -import RacingBar from './RacingBar'; +import RacingBar, { MediaControlers } from './RacingBar'; import { RepoActivityDetails } from '.'; interface Props { @@ -15,7 +15,7 @@ interface Props { const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { const [options, setOptions] = useState(defaults); - const [replay, setReplay] = useState(0); + const mediaControlersRef = useRef(null); useEffect(() => { (async function () { @@ -23,10 +23,6 @@ const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { })(); }, []); - const handleReplayClick = () => { - setReplay(replay + 1); - }; - return (
@@ -38,19 +34,34 @@ const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { )}
- + +
diff --git a/src/pages/ContentScripts/index.scss b/src/pages/ContentScripts/index.scss index 94d33c3e..23873131 100644 --- a/src/pages/ContentScripts/index.scss +++ b/src/pages/ContentScripts/index.scss @@ -110,7 +110,7 @@ user-select: none; /* Standard */ } -.replay-button { +.perceptor-button { display: inline-block; text-align: center; text-decoration: none;