From a55523ad1678b3e25b951750e6da5aebce547ef1 Mon Sep 17 00:00:00 2001 From: Zi1l <1260497933@qq.com> Date: Wed, 20 Sep 2023 21:15:29 +0800 Subject: [PATCH 1/5] add RacingBar Record --- INSTALLATION.md | 4 +- INSTALLATION.zh-CN.md | 6 +- README.md | 10 +--- README.zh-CN.md | 26 ++++---- scripts/bump-version.cjs | 27 ++++++--- .../repo-activity-racing-bar/RacingBar.tsx | 11 +++- .../repo-activity-racing-bar/view.tsx | 59 ++++++++++++++++++- 7 files changed, 103 insertions(+), 40 deletions(-) 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..da54ef2f 100644 --- a/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx +++ b/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx @@ -5,7 +5,7 @@ import React, { useEffect, useState, useRef } from 'react'; import * as echarts from 'echarts'; import type { EChartsOption, EChartsType, BarSeriesOption } from 'echarts'; import { Spin } from 'antd'; - +import { mediaRecorder, recordingStarted, setRecordingStarted } from './view'; interface RacingBarProps { repoName: string; data: RepoActivityDetails; @@ -150,6 +150,15 @@ const play = (instance: EChartsType, data: RepoActivityDetails) => { if (i < months.length) { timer = setTimeout(playNext, updateFrequency); } + if (i == months.length - 1) { + // Stop the media recorder after the animation finishes + instance.on('finished', function () { + if (recordingStarted) { + setRecordingStarted(false); + mediaRecorder.stop(); + } + }); + } }; playNext(); 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..cfaa5498 100644 --- a/src/pages/ContentScripts/features/repo-activity-racing-bar/view.tsx +++ b/src/pages/ContentScripts/features/repo-activity-racing-bar/view.tsx @@ -7,12 +7,17 @@ import optionsStorage, { } from '../../../../options-storage'; import RacingBar from './RacingBar'; import { RepoActivityDetails } from '.'; +// import RecordRTC; interface Props { currentRepo: string; repoActivityDetails: RepoActivityDetails; } - +export let recordingStarted = false; +export function setRecordingStarted(status: boolean) { + recordingStarted = status; +} +export let mediaRecorder: MediaRecorder; const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { const [options, setOptions] = useState(defaults); const [replay, setReplay] = useState(0); @@ -27,6 +32,54 @@ const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { setReplay(replay + 1); }; + recordingStarted = false; + + // Event listener for the start button + const exportToVideo = () => { + // Start the recording only if it has not already started + if (!recordingStarted) { + recordingStarted = true; + // Capture the canvas element + + const canvas = document.querySelector( + '#hypercrx-repo-activity-racing-bar > div > div > div.d-flex.flex-wrap.flex-items-center > div.col-12.col-md-8 > div > div > div > div > canvas' + ) as HTMLCanvasElement; + const stream = canvas.captureStream(); + + // Start the media recorder + const chunks: Blob[] = []; + mediaRecorder = new MediaRecorder(stream, { + mimeType: 'video/webm; codecs=vp9', + }); + + mediaRecorder.ondataavailable = function (event) { + chunks.push(event.data); + }; + + // Start recording + mediaRecorder.start(); + + // Handle the stop event + mediaRecorder.onstop = function () { + const blob = new Blob(chunks, { type: 'video/webm' }); + 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.webm'; + a.href = url; + a.click(); + + // Clean up + URL.revokeObjectURL(url); + }; + } + }; + return (
@@ -44,6 +97,10 @@ const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { options.locale )} + +
From f66a485956d483330ff5b095fff023fc2f46a9cf Mon Sep 17 00:00:00 2001 From: Zi1l <1260497933@qq.com> Date: Fri, 22 Sep 2023 17:10:17 +0800 Subject: [PATCH 2/5] export racing bar video --- .../repo-activity-racing-bar/RacingBar.tsx | 16 +++---- .../repo-activity-racing-bar/view.tsx | 46 +++++++++++++------ 2 files changed, 39 insertions(+), 23 deletions(-) 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 da54ef2f..fa113ede 100644 --- a/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx +++ b/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx @@ -3,9 +3,10 @@ import { avatarColorStore } from './AvatarColorStore'; import React, { useEffect, useState, useRef } from 'react'; import * as echarts from 'echarts'; -import type { EChartsOption, EChartsType, BarSeriesOption } from 'echarts'; import { Spin } from 'antd'; -import { mediaRecorder, recordingStarted, setRecordingStarted } from './view'; +import type { BarSeriesOption, EChartsOption, EChartsType } from 'echarts'; +import { stopRecording } from './view'; + interface RacingBarProps { repoName: string; data: RepoActivityDetails; @@ -152,12 +153,11 @@ const play = (instance: EChartsType, data: RepoActivityDetails) => { } if (i == months.length - 1) { // Stop the media recorder after the animation finishes - instance.on('finished', function () { - if (recordingStarted) { - setRecordingStarted(false); - mediaRecorder.stop(); - } - }); + // instance.on('finished', function () { + stopRecording(); + const rec = document.getElementById('rec'); + if(rec!=null) rec.innerText = 'record' + // }); } }; 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 cfaa5498..68a86d69 100644 --- a/src/pages/ContentScripts/features/repo-activity-racing-bar/view.tsx +++ b/src/pages/ContentScripts/features/repo-activity-racing-bar/view.tsx @@ -13,11 +13,22 @@ interface Props { currentRepo: string; repoActivityDetails: RepoActivityDetails; } -export let recordingStarted = false; -export function setRecordingStarted(status: boolean) { - recordingStarted = status; -} + +let recordingStarted = false; export let mediaRecorder: MediaRecorder; +export function stopRecording() { + console.log('before stop media ' + recordingStarted); + if (recordingStarted) { + recordingStarted = false; + console.log('stop media'); + mediaRecorder.stop(); + console.log('------- ed -------'); + const rec = document.getElementById('rec'); + // @ts-ignore + rec.innerText = 'record'; + + } +} const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { const [options, setOptions] = useState(defaults); const [replay, setReplay] = useState(0); @@ -32,30 +43,34 @@ const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { setReplay(replay + 1); }; - recordingStarted = false; - + // Event listener for the start button const exportToVideo = () => { // Start the recording only if it has not already started if (!recordingStarted) { recordingStarted = true; // Capture the canvas element - - const canvas = document.querySelector( - '#hypercrx-repo-activity-racing-bar > div > div > div.d-flex.flex-wrap.flex-items-center > div.col-12.col-md-8 > div > div > div > div > canvas' - ) as HTMLCanvasElement; + console.log('start media ' + recordingStarted); + const canvas = document.querySelector('#hypercrx-repo-activity-racing-bar > div > div > div.d-flex.flex-wrap.flex-items-center > div.col-12.col-md-8 > div > div > div > div > div > div > canvas') as HTMLCanvasElement; + const rec = document.getElementById('rec'); + + if (!canvas || !rec) { + return; + } + + rec.innerText = '录制中,点击停止录制并下载'; const stream = canvas.captureStream(); - + // Start the media recorder const chunks: Blob[] = []; mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm; codecs=vp9', }); - + mediaRecorder.ondataavailable = function (event) { chunks.push(event.data); }; - + handleReplayClick(); // Start recording mediaRecorder.start(); @@ -67,7 +82,6 @@ const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { // 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.webm'; @@ -77,6 +91,8 @@ const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { // Clean up URL.revokeObjectURL(url); }; + + rec.onclick = stopRecording; } }; @@ -98,7 +114,7 @@ const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { )} -
From cc4c2e096b5cf33b372666af3e4fe0ada8fd2358 Mon Sep 17 00:00:00 2001 From: Zi1l <1260497933@qq.com> Date: Fri, 22 Sep 2023 18:30:32 +0800 Subject: [PATCH 3/5] run prettier --- .../repo-activity-racing-bar/RacingBar.tsx | 6 +++--- .../features/repo-activity-racing-bar/view.tsx | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) 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 fa113ede..869e3d33 100644 --- a/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx +++ b/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx @@ -154,9 +154,9 @@ const play = (instance: EChartsType, data: RepoActivityDetails) => { if (i == months.length - 1) { // Stop the media recorder after the animation finishes // instance.on('finished', function () { - stopRecording(); - const rec = document.getElementById('rec'); - if(rec!=null) rec.innerText = 'record' + stopRecording(); + const rec = document.getElementById('rec'); + if (rec != null) rec.innerText = 'record'; // }); } }; 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 68a86d69..5a1cacd8 100644 --- a/src/pages/ContentScripts/features/repo-activity-racing-bar/view.tsx +++ b/src/pages/ContentScripts/features/repo-activity-racing-bar/view.tsx @@ -26,7 +26,6 @@ export function stopRecording() { const rec = document.getElementById('rec'); // @ts-ignore rec.innerText = 'record'; - } } const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { @@ -43,7 +42,6 @@ const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { setReplay(replay + 1); }; - // Event listener for the start button const exportToVideo = () => { // Start the recording only if it has not already started @@ -51,22 +49,24 @@ const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { recordingStarted = true; // Capture the canvas element console.log('start media ' + recordingStarted); - const canvas = document.querySelector('#hypercrx-repo-activity-racing-bar > div > div > div.d-flex.flex-wrap.flex-items-center > div.col-12.col-md-8 > div > div > div > div > div > div > canvas') as HTMLCanvasElement; + const canvas = document.querySelector( + '#hypercrx-repo-activity-racing-bar > div > div > div.d-flex.flex-wrap.flex-items-center > div.col-12.col-md-8 > div > div > div > div > div > div > canvas' + ) as HTMLCanvasElement; const rec = document.getElementById('rec'); - + if (!canvas || !rec) { return; } - + rec.innerText = '录制中,点击停止录制并下载'; const stream = canvas.captureStream(); - + // Start the media recorder const chunks: Blob[] = []; mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm; codecs=vp9', }); - + mediaRecorder.ondataavailable = function (event) { chunks.push(event.data); }; From bbc694251351f653cbaa4eab3d153a7dc968ebcf Mon Sep 17 00:00:00 2001 From: Lam Tang Date: Fri, 22 Sep 2023 22:12:07 +0800 Subject: [PATCH 4/5] refactor: refactored in the React way but the origin-clean error not solved --- .../repo-activity-racing-bar/RacingBar.tsx | 178 ++++++++++++------ .../repo-activity-racing-bar/view.tsx | 91 ++------- src/pages/ContentScripts/index.scss | 2 +- 3 files changed, 137 insertions(+), 134 deletions(-) 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 869e3d33..211ed3be 100644 --- a/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx +++ b/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx @@ -1,11 +1,22 @@ 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 { Spin } from 'antd'; import type { BarSeriesOption, EChartsOption, EChartsType } from 'echarts'; -import { stopRecording } from './view'; + +export interface RecordingHandlers { + startRecording: () => void; + stopRecording: () => void; +} interface RacingBarProps { repoName: string; @@ -151,14 +162,6 @@ const play = (instance: EChartsType, data: RepoActivityDetails) => { if (i < months.length) { timer = setTimeout(playNext, updateFrequency); } - if (i == months.length - 1) { - // Stop the media recorder after the animation finishes - // instance.on('finished', function () { - stopRecording(); - const rec = document.getElementById('rec'); - if (rec != null) rec.innerText = 'record'; - // }); - } }; playNext(); @@ -191,58 +194,117 @@ 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([]); - useEffect(() => { - (async () => { - if (!divEL.current) return; + 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); + const chartDOM = divEL.current; + const instance = echarts.init(chartDOM); - // load avatars and extract colors before playing the chart - const promises = contributors.map(async (contributor) => { - await avatarColorStore.getColors(contributor); - setLoadedAvatars((loadedAvatars) => loadedAvatars + 1); + // 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); + + play(instance, data); + + return () => { + if (!instance.isDisposed()) { + instance.dispose(); + } + // clear timer if user replay the chart before it finishes + if (timer) { + clearTimeout(timer); + } + }; + })(); + }, []); + + const startRecording = () => { + if (!divEL.current) return; + + console.log('start record'); + // 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 record'); + // 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, () => ({ + 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 5a1cacd8..8764cc5f 100644 --- a/src/pages/ContentScripts/features/repo-activity-racing-bar/view.tsx +++ b/src/pages/ContentScripts/features/repo-activity-racing-bar/view.tsx @@ -1,36 +1,22 @@ -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, { RecordingHandlers } from './RacingBar'; import { RepoActivityDetails } from '.'; -// import RecordRTC; interface Props { currentRepo: string; repoActivityDetails: RepoActivityDetails; } -let recordingStarted = false; -export let mediaRecorder: MediaRecorder; -export function stopRecording() { - console.log('before stop media ' + recordingStarted); - if (recordingStarted) { - recordingStarted = false; - console.log('stop media'); - mediaRecorder.stop(); - console.log('------- ed -------'); - const rec = document.getElementById('rec'); - // @ts-ignore - rec.innerText = 'record'; - } -} const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { const [options, setOptions] = useState(defaults); const [replay, setReplay] = useState(0); + const recordRef = useRef(null); useEffect(() => { (async function () { @@ -42,60 +28,6 @@ const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { setReplay(replay + 1); }; - // Event listener for the start button - const exportToVideo = () => { - // Start the recording only if it has not already started - if (!recordingStarted) { - recordingStarted = true; - // Capture the canvas element - console.log('start media ' + recordingStarted); - const canvas = document.querySelector( - '#hypercrx-repo-activity-racing-bar > div > div > div.d-flex.flex-wrap.flex-items-center > div.col-12.col-md-8 > div > div > div > div > div > div > canvas' - ) as HTMLCanvasElement; - const rec = document.getElementById('rec'); - - if (!canvas || !rec) { - return; - } - - rec.innerText = '录制中,点击停止录制并下载'; - const stream = canvas.captureStream(); - - // Start the media recorder - const chunks: Blob[] = []; - mediaRecorder = new MediaRecorder(stream, { - mimeType: 'video/webm; codecs=vp9', - }); - - mediaRecorder.ondataavailable = function (event) { - chunks.push(event.data); - }; - handleReplayClick(); - // Start recording - mediaRecorder.start(); - - // Handle the stop event - mediaRecorder.onstop = function () { - const blob = new Blob(chunks, { type: 'video/webm' }); - 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.webm'; - a.href = url; - a.click(); - - // Clean up - URL.revokeObjectURL(url); - }; - - rec.onclick = stopRecording; - } - }; - return (
@@ -107,15 +39,23 @@ const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { )}
- - - +
@@ -124,6 +64,7 @@ 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; From 4022cf560091ed5a029a84816b650f8ccc649d55 Mon Sep 17 00:00:00 2001 From: Lam Tang Date: Fri, 22 Sep 2023 22:47:53 +0800 Subject: [PATCH 5/5] refactor: a real replay button --- .../repo-activity-racing-bar/RacingBar.tsx | 43 +++++++++++-------- .../repo-activity-racing-bar/view.tsx | 21 ++++----- 2 files changed, 33 insertions(+), 31 deletions(-) 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 211ed3be..993674d8 100644 --- a/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx +++ b/src/pages/ContentScripts/features/repo-activity-racing-bar/RacingBar.tsx @@ -13,7 +13,8 @@ import * as echarts from 'echarts'; import { Spin } from 'antd'; import type { BarSeriesOption, EChartsOption, EChartsType } from 'echarts'; -export interface RecordingHandlers { +export interface MediaControlers { + play: () => void; startRecording: () => void; stopRecording: () => void; } @@ -152,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; @@ -197,7 +198,7 @@ const countLongTermContributors = ( const RacingBar = forwardRef( ( { data }: RacingBarProps, - forwardedRef: ForwardedRef + forwardedRef: ForwardedRef ): JSX.Element => { const [loadedAvatars, setLoadedAvatars] = useState(0); const divEL = useRef(null); @@ -217,9 +218,6 @@ const RacingBar = forwardRef( (async () => { if (!divEL.current) return; - const chartDOM = divEL.current; - const instance = echarts.init(chartDOM); - // load avatars and extract colors before playing the chart const promises = contributors.map(async (contributor) => { await avatarColorStore.getColors(contributor); @@ -227,24 +225,30 @@ const RacingBar = forwardRef( }); 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); - } - }; + 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; + instance = echarts.init(chartDOM); + playFromStart(instance, data); + }; + const startRecording = () => { if (!divEL.current) return; - console.log('start record'); + console.log('start recording'); // Start the media recorder const canvas: HTMLCanvasElement = divEL.current.querySelector('div > canvas')!; @@ -264,7 +268,7 @@ const RacingBar = forwardRef( const stopRecording = () => { if (!mediaRecorderRef.current) return; - console.log('stop record'); + console.log('stop recording'); // Handle the stop event mediaRecorderRef.current.onstop = function () { const blob = new Blob(chunksRef.current, { type: 'video/mp4' }); @@ -289,6 +293,7 @@ const RacingBar = forwardRef( // expose startRecording and stopRecording to parent component useImperativeHandle(forwardedRef, () => ({ + play, startRecording, stopRecording, })); 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 8764cc5f..252b0b4c 100644 --- a/src/pages/ContentScripts/features/repo-activity-racing-bar/view.tsx +++ b/src/pages/ContentScripts/features/repo-activity-racing-bar/view.tsx @@ -5,7 +5,7 @@ import optionsStorage, { HypercrxOptions, defaults, } from '../../../../options-storage'; -import RacingBar, { RecordingHandlers } from './RacingBar'; +import RacingBar, { MediaControlers } from './RacingBar'; import { RepoActivityDetails } from '.'; interface Props { @@ -15,8 +15,7 @@ interface Props { const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { const [options, setOptions] = useState(defaults); - const [replay, setReplay] = useState(0); - const recordRef = useRef(null); + const mediaControlersRef = useRef(null); useEffect(() => { (async function () { @@ -24,10 +23,6 @@ const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { })(); }, []); - const handleReplayClick = () => { - setReplay(replay + 1); - }; - return (
@@ -39,7 +34,10 @@ const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => { )}
- @@ -63,8 +61,7 @@ const View = ({ currentRepo, repoActivityDetails }: Props): JSX.Element => {