From 14df77c36f799b681c21dd47cae9b57650205873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E4=BD=91=E5=A9=89=E5=BC=98?= <2585506997@qq.com> Date: Tue, 30 Apr 2024 16:47:06 +0800 Subject: [PATCH 01/18] init colorful-calendar --- .../features/colorful-calendar/index.tsx | 20 +++++++++++++++++++ src/pages/ContentScripts/index.ts | 1 + 2 files changed, 21 insertions(+) create mode 100644 src/pages/ContentScripts/features/colorful-calendar/index.tsx diff --git a/src/pages/ContentScripts/features/colorful-calendar/index.tsx b/src/pages/ContentScripts/features/colorful-calendar/index.tsx new file mode 100644 index 00000000..7f1203c7 --- /dev/null +++ b/src/pages/ContentScripts/features/colorful-calendar/index.tsx @@ -0,0 +1,20 @@ +import features from '../../../../feature-manager'; + +import * as pageDetect from 'github-url-detection'; + +const featureId = features.getFeatureID(import.meta.url); + +const init = async (): Promise => { + console.log('init colorful-calendar'); +}; + +const restore = async () => { + console.log('restore colorful-calendar'); +}; + +features.add(featureId, { + asLongAs: [pageDetect.isUserProfile], + awaitDomReady: false, + init, + restore, +}); diff --git a/src/pages/ContentScripts/index.ts b/src/pages/ContentScripts/index.ts index bab274a3..197bfb02 100644 --- a/src/pages/ContentScripts/index.ts +++ b/src/pages/ContentScripts/index.ts @@ -1,5 +1,6 @@ import './index.scss'; +import './features/colorful-calendar'; import './features/repo-activity-openrank-trends'; import './features/developer-activity-openrank-trends'; import './features/repo-header-labels'; From 873b31cda22834784ec99e3637bb679363df46b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E4=BD=91=E5=A9=89=E5=BC=98?= <2585506997@qq.com> Date: Tue, 30 Apr 2024 16:51:47 +0800 Subject: [PATCH 02/18] change calendar's color --- .../ContentScripts/features/colorful-calendar/index.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pages/ContentScripts/features/colorful-calendar/index.tsx b/src/pages/ContentScripts/features/colorful-calendar/index.tsx index 7f1203c7..0179cd59 100644 --- a/src/pages/ContentScripts/features/colorful-calendar/index.tsx +++ b/src/pages/ContentScripts/features/colorful-calendar/index.tsx @@ -6,6 +6,11 @@ const featureId = features.getFeatureID(import.meta.url); const init = async (): Promise => { console.log('init colorful-calendar'); + const root = document.documentElement; + root.style.setProperty('--color-calendar-graph-day-L1-bg', '#ffedf9'); + root.style.setProperty('--color-calendar-graph-day-L2-bg', '#ffc3eb'); + root.style.setProperty('--color-calendar-graph-day-L3-bg', '#ff3ebf'); + root.style.setProperty('--color-calendar-graph-day-L4-bg', '#c70085'); }; const restore = async () => { From a763ef5df071e755baf551763d519a07466ff3e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E4=BD=91=E5=A9=89=E5=BC=98?= <2585506997@qq.com> Date: Tue, 30 Apr 2024 17:12:22 +0800 Subject: [PATCH 03/18] add ColorPicker --- .../features/colorful-calendar/index.scss | 19 +++++++ .../features/colorful-calendar/index.tsx | 53 ++++++++++++++++--- 2 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 src/pages/ContentScripts/features/colorful-calendar/index.scss diff --git a/src/pages/ContentScripts/features/colorful-calendar/index.scss b/src/pages/ContentScripts/features/colorful-calendar/index.scss new file mode 100644 index 00000000..daad3949 --- /dev/null +++ b/src/pages/ContentScripts/features/colorful-calendar/index.scss @@ -0,0 +1,19 @@ +.ant-color-picker-trigger { + min-width: 10px !important; + padding: 0 !important; + margin-right: 4px; + border: none !important; +} + +.ant-color-picker-color-block { + width: 10px !important; + min-width: 10px !important; + height: 10px !important; +} + +.ant-color-picker-color-block-inner { + width: 10px !important; + min-width: 10px !important; + height: 10px !important; + border-radius: 3px !important; +} diff --git a/src/pages/ContentScripts/features/colorful-calendar/index.tsx b/src/pages/ContentScripts/features/colorful-calendar/index.tsx index 0179cd59..6c208acc 100644 --- a/src/pages/ContentScripts/features/colorful-calendar/index.tsx +++ b/src/pages/ContentScripts/features/colorful-calendar/index.tsx @@ -1,16 +1,57 @@ import features from '../../../../feature-manager'; +import waitFor from '../../../../helpers/wait-for'; +import React from 'react'; +import { render } from 'react-dom'; +import { ColorPicker } from 'antd'; +import $ from 'jquery'; import * as pageDetect from 'github-url-detection'; +import './index.scss'; // 需要引入自定义的样式来覆盖antd ColorPicker的默认样式,后面展开说明 + const featureId = features.getFeatureID(import.meta.url); -const init = async (): Promise => { - console.log('init colorful-calendar'); +const CALENDAR_LEVEL_COLORS = [ + '#ebedf0', + '#ffedf9', + '#ffc3eb', + '#ff3ebf', + '#c70085', +]; + +const changeLevelColor = (level: number, color: string) => { const root = document.documentElement; - root.style.setProperty('--color-calendar-graph-day-L1-bg', '#ffedf9'); - root.style.setProperty('--color-calendar-graph-day-L2-bg', '#ffc3eb'); - root.style.setProperty('--color-calendar-graph-day-L3-bg', '#ff3ebf'); - root.style.setProperty('--color-calendar-graph-day-L4-bg', '#c70085'); + if (level === 0) { + root.style.setProperty(`--color-calendar-graph-day-bg`, color); + } else { + root.style.setProperty(`--color-calendar-graph-day-L${level}-bg`, color); + } +}; + +const replaceLegendToColorPicker = async ( + level: number, + defaultColor: string +) => { + const legendSelector = `#contribution-graph-legend-level-${level}`; // 选择器selector是用于定位DOM元素的字符串 + await waitFor(() => $(legendSelector).length > 0); // init函数运行的时候,页面中某些元素不一定已经加载完毕,经过测试,日历图加载时机比较靠后,因此需要waitFor一下,不然后面的操作都是无用的 + const $legend = $(legendSelector); + const container = $('
'); + render( + changeLevelColor(level, hex)} + />, // 选择新颜色后会调用changeLevelColor改变格子颜色 + container[0] + ); // 将React组件渲染为真实的DOM元素 + $legend.replaceWith(container); // 使用jQuery的replaceWith方法将图例格子替换为ColorPicker +}; + +const init = async (): Promise => { + for (let i = 0; i < CALENDAR_LEVEL_COLORS.length; i++) { + changeLevelColor(i, CALENDAR_LEVEL_COLORS[i]); // 初始化时就按照给定的颜色改变日历格子的颜色 + await replaceLegendToColorPicker(i, CALENDAR_LEVEL_COLORS[i]); + } }; const restore = async () => { From f9d5ea0359363bf00c250c98b928269804d419a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E4=BD=91=E5=A9=89=E5=BC=98?= <2585506997@qq.com> Date: Fri, 3 May 2024 23:20:16 +0800 Subject: [PATCH 04/18] restore colorful-calendar --- .../features/colorful-calendar/index.tsx | 53 +++++++++++++++---- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/src/pages/ContentScripts/features/colorful-calendar/index.tsx b/src/pages/ContentScripts/features/colorful-calendar/index.tsx index 6c208acc..50707821 100644 --- a/src/pages/ContentScripts/features/colorful-calendar/index.tsx +++ b/src/pages/ContentScripts/features/colorful-calendar/index.tsx @@ -11,21 +11,38 @@ import './index.scss'; // 需要引入自定义的样式来覆盖antd ColorPicke const featureId = features.getFeatureID(import.meta.url); -const CALENDAR_LEVEL_COLORS = [ - '#ebedf0', - '#ffedf9', - '#ffc3eb', - '#ff3ebf', - '#c70085', -]; +// const CALENDAR_LEVEL_COLORS = [ +// '#ebedf0', +// '#ffedf9', +// '#ffc3eb', +// '#ff3ebf', +// '#c70085', +// ]; +// +// const changeLevelColor = (level: number, color: string) => { +// const root = document.documentElement; +// if (level === 0) { +// root.style.setProperty(`--color-calendar-graph-day-bg`, color); +// } else { +// root.style.setProperty(`--color-calendar-graph-day-L${level}-bg`, color); +// } +// }; -const changeLevelColor = (level: number, color: string) => { +let colors = ['#ebedf0', '#ffedf9', '#ffc3eb', '#ff3ebf', '#c70085']; + +const changeLevelColor = async (level: number, color: string) => { const root = document.documentElement; if (level === 0) { root.style.setProperty(`--color-calendar-graph-day-bg`, color); } else { root.style.setProperty(`--color-calendar-graph-day-L${level}-bg`, color); } + // Save to storage + const newColors = [...colors]; + newColors[level] = color; + await chrome.storage.local.set({ + calendar_level_colors: newColors, + }); }; const replaceLegendToColorPicker = async ( @@ -47,10 +64,24 @@ const replaceLegendToColorPicker = async ( $legend.replaceWith(container); // 使用jQuery的replaceWith方法将图例格子替换为ColorPicker }; +// const init = async (): Promise => { +// for (let i = 0; i < CALENDAR_LEVEL_COLORS.length; i++) { +// changeLevelColor(i, CALENDAR_LEVEL_COLORS[i]); // 初始化时就按照给定的颜色改变日历格子的颜色 +// await replaceLegendToColorPicker(i, CALENDAR_LEVEL_COLORS[i]); +// } +// }; + const init = async (): Promise => { - for (let i = 0; i < CALENDAR_LEVEL_COLORS.length; i++) { - changeLevelColor(i, CALENDAR_LEVEL_COLORS[i]); // 初始化时就按照给定的颜色改变日历格子的颜色 - await replaceLegendToColorPicker(i, CALENDAR_LEVEL_COLORS[i]); + // Load colors from storage + colors = + (await chrome.storage.local.get('calendar_level_colors'))[ + // (await localStorage.get('calendar_level_colors'))[ + 'calendar_level_colors' + ] || colors; + + for (let i = 0; i < colors.length; i++) { + changeLevelColor(i, colors[i]); + replaceLegendToColorPicker(i, colors[i]); } }; From 4631e3320f513cc776343b87e007f8601d096567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E4=BD=91=E5=A9=89=E5=BC=98?= <2585506997@qq.com> Date: Mon, 22 Jul 2024 12:41:42 +0800 Subject: [PATCH 05/18] add community openrank api --- src/api/community.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/api/community.ts diff --git a/src/api/community.ts b/src/api/community.ts new file mode 100644 index 00000000..5c16d347 --- /dev/null +++ b/src/api/community.ts @@ -0,0 +1,22 @@ +import request from '../helpers/request'; +import { ErrorCode, OSS_XLAB_ENDPOINT } from '../constant'; + +export const getMetricByDate = async (repoName: string, date: string) => { + try { + return await request( + `${OSS_XLAB_ENDPOINT}/open_digger/github/${repoName}/project_openrank_detail/${date}.json` + ); + } catch (error) { + // the catched error being "404" means the metric file is not available so return a null + if (error === ErrorCode.NOT_FOUND) { + return null; + } else { + // other errors should be throwed + throw error; + } + } +}; + +export const getOpenrank = async (repo: string, date: string) => { + return getMetricByDate(repo, date); +}; From 0a7b9d03463461ce309edea947f884e9bbe8c53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E4=BD=91=E5=A9=89=E5=BC=98?= <2585506997@qq.com> Date: Mon, 22 Jul 2024 13:51:56 +0800 Subject: [PATCH 06/18] add community openrank feature container to perceptor --- src/pages/ContentScripts/features/perceptor-layout/view.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/ContentScripts/features/perceptor-layout/view.tsx b/src/pages/ContentScripts/features/perceptor-layout/view.tsx index cbb9f79a..d3b491b4 100644 --- a/src/pages/ContentScripts/features/perceptor-layout/view.tsx +++ b/src/pages/ContentScripts/features/perceptor-layout/view.tsx @@ -5,6 +5,8 @@ const View = (): JSX.Element => { <>
+
+
); }; From 17f03b843c16e2123c0187f9d20fd2c445701655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E4=BD=91=E5=A9=89=E5=BC=98?= <2585506997@qq.com> Date: Tue, 23 Jul 2024 02:11:03 +0800 Subject: [PATCH 07/18] add Community OpenRank Detail Network --- src/api/community.ts | 4 +- src/locales/en/translation.json | 2 + src/locales/zh_CN/translation.json | 2 + .../community-openrank-network/Network.tsx | 219 ++++++++++++++++++ .../community-openrank-network/index.scss | 31 +++ .../community-openrank-network/index.tsx | 60 +++++ .../community-openrank-network/view.tsx | 71 ++++++ src/pages/ContentScripts/index.ts | 1 + 8 files changed, 387 insertions(+), 3 deletions(-) create mode 100644 src/pages/ContentScripts/features/community-openrank-network/Network.tsx create mode 100644 src/pages/ContentScripts/features/community-openrank-network/index.scss create mode 100644 src/pages/ContentScripts/features/community-openrank-network/index.tsx create mode 100644 src/pages/ContentScripts/features/community-openrank-network/view.tsx diff --git a/src/api/community.ts b/src/api/community.ts index 5c16d347..bd62792c 100644 --- a/src/api/community.ts +++ b/src/api/community.ts @@ -3,9 +3,7 @@ import { ErrorCode, OSS_XLAB_ENDPOINT } from '../constant'; export const getMetricByDate = async (repoName: string, date: string) => { try { - return await request( - `${OSS_XLAB_ENDPOINT}/open_digger/github/${repoName}/project_openrank_detail/${date}.json` - ); + return await request(`${OSS_XLAB_ENDPOINT}/open_digger/github/${repoName}/project_openrank_detail/${date}.json`); } catch (error) { // the catched error being "404" means the metric file is not available so return a null if (error === ErrorCode.NOT_FOUND) { diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index cf9b6f6a..57de238b 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -3,6 +3,8 @@ "global_day_one": "day", "global_day_other": "days", "global_clickToshow": "Click to show", + "component_communityOpenRankNetwork_title": "Community OpenRank Detail Network", + "component_communityOpenRankNetwork_description": "Community OpenRank Detail Network shows the OpenRank about the project by month. Double-click a node in this network to view the details of OpenRank for that node in the table on the right. ", "component_developerCollaborationNetwork_title": "Developer Collaboration Network", "component_developerCollaborationNetwork_description": "Developer Collaboration Network shows the collaboration between developers for a given time period. From this graph you can find other developers who are closet to a given developer.", "component_developerCollaborationNetwork_description_node": "Node: Developer, node size and shades of color indicate developer activity.", diff --git a/src/locales/zh_CN/translation.json b/src/locales/zh_CN/translation.json index f4181599..daa32331 100644 --- a/src/locales/zh_CN/translation.json +++ b/src/locales/zh_CN/translation.json @@ -2,6 +2,8 @@ "global_period": "周期", "global_day": "天", "global_clickToshow": "点击查看", + "component_communityOpenRankNetwork_title": "社区OpenRank网络图", + "component_communityOpenRankNetwork_description": "社区OpenRank网络图按月显示有关该项目的OpenRank。双击此网络中的一个节点,可以在右侧表格中查看该节点OpenRank详细信息。 ", "component_developerCollaborationNetwork_title": "开发者协作网络图", "component_developerCollaborationNetwork_description": "开发者协作网络图展示了在给定的时间段内,开发者与开发者之间的协作关系, 用于开发者关系的追踪与挖掘。从该网络图中,可以找出与该开发者联系较为紧密的其他开发者。", "component_developerCollaborationNetwork_description_node": "节点:一个节点表示开发者,节点大小与颜色的深浅表示开发者活跃度的大小。", diff --git a/src/pages/ContentScripts/features/community-openrank-network/Network.tsx b/src/pages/ContentScripts/features/community-openrank-network/Network.tsx new file mode 100644 index 00000000..fe335313 --- /dev/null +++ b/src/pages/ContentScripts/features/community-openrank-network/Network.tsx @@ -0,0 +1,219 @@ +import React, { CSSProperties, useEffect, useRef } from 'react'; +import * as echarts from 'echarts'; + +import linearMap from '../../../../helpers/linear-map'; +import { debounce } from 'lodash-es'; +import getGithubTheme from '../../../../helpers/get-github-theme'; + +interface NetworkProps { + /** + * data + */ + readonly data: any; + /** + * `style` for graph container + */ + readonly style?: CSSProperties; + /** + * callback function when click node + */ + readonly focusedNodeID?: string; + + date?: string; +} + +const typeMap = new Map([ + ['r', 'repo'], + ['i', 'issue'], + ['p', 'pull'], + ['u', 'user'], +]); + +const genName = (node: { c: string; n: { toString: () => any } }) => + node.c == 'i' || node.c == 'p' ? `#${node.n.toString()}` : node.n.toString(); + +const categories = Array.from(typeMap.values()); + +const theme = getGithubTheme(); +const DARK_TEXT_COLOR = 'rgba(230, 237, 243, 0.9)'; + +const generateEchartsData = (data: any, focusedNodeID: string | undefined): any => { + const generateNodes = (nodes: any[]): any => { + return nodes.map((n: any) => { + return { + id: n.id, + name: genName(n), + value: n.v, + symbolSize: Math.log(n.v + 1) * 6, + category: typeMap.get(n.c), + }; + }); + }; + const generateEdges = (edges: any[]): any => { + if (edges.length === 0) { + return []; + } + return edges.map((e: any) => { + return { + source: e.s, + target: e.t, + value: e.w, + }; + }); + }; + return { + nodes: generateNodes(data.nodes), + edges: generateEdges(data.links), + }; +}; + +const Network: React.FC = ({ data, style = {}, focusedNodeID, date }) => { + const divEL = useRef(null); + const graphData = generateEchartsData(data, focusedNodeID); + const option = { + tooltip: { + trigger: 'item', + }, + animation: true, + animationDuration: 2000, + // title: { + // text: `OpenRank details for ${focusedNodeID} in ${date}`, + // top: 'bottom', + // left: 'right' + // }, + legend: [ + { + data: categories, + }, + ], + series: [ + { + name: 'Collaborative graph', + type: 'graph', + layout: 'force', + nodes: graphData.nodes, + edges: graphData.edges, + categories: categories.map((c) => { + return { name: c }; + }), + // Enable mouse zooming and translating + roam: true, + label: { + position: 'right', + show: true, + }, + force: { + // initLayout: 'circular', + // gravity: 0.1, + repulsion: 300, + // edgeLength: [50, 100], + // Disable the iteration animation of layout + layoutAnimation: false, + }, + lineStyle: { + curveness: 0.3, + opacity: 0.2, + }, + emphasis: { + focus: 'adjacency', + label: { + position: 'right', + show: true, + }, + }, + }, + ], + graphic: { + elements: [ + { + type: 'text', + right: 60, + bottom: 60, + style: { + text: date, + font: 'bolder 60px monospace', + fill: theme === 'light' ? 'rgba(100, 100, 100, 0.3)' : DARK_TEXT_COLOR, + }, + z: 100, + }, + ], + }, + }; + + const clearDiv = (id: string) => { + var div = document.getElementById(id); + if (div && div.hasChildNodes()) { + var children = div.childNodes; + for (var child of children) { + div.removeChild(child); + } + } + }; + + const addRow = (table: HTMLElement | null, texts: any[]) => { + // @ts-ignore + var tr = table.insertRow(); + for (var t of texts) { + var td = tr.insertCell(); + td.appendChild(document.createTextNode(t)); + } + }; + + const setDetails = (graph: { links: any[]; nodes: any[] }, node: { r: number; i: number; id: any }) => { + clearDiv('details_table'); + var table = document.getElementById('details_table'); + addRow(table, ['From', 'Ratio', 'Value', 'OpenRank']); + addRow(table, ['Self', node.r, node.i, (node.r * node.i).toFixed(3)]); + var other = graph.links + .filter((l) => l.t == node.id) + .map((l) => { + var source = graph.nodes.find((n) => n.id == l.s); + return [ + genName(source), + parseFloat(((1 - node.r) * l.w).toFixed(3)), + source.v, + parseFloat(((1 - node.r) * l.w * source.v).toFixed(3)), + ]; + }) + .sort((a, b) => b[3] - a[3]); + for (var r of other) { + addRow(table, r); + } + }; + + useEffect(() => { + let chartDOM = divEL.current; + const instance = echarts.init(chartDOM as any); + + return () => { + instance.dispose(); + }; + }, []); + + useEffect(() => { + let chartDOM = divEL.current; + const instance = echarts.getInstanceByDom(chartDOM as any); + if (instance) { + instance.setOption(option); + instance.on('dblclick', function (params) { + setDetails( + data, + // @ts-ignore + data.nodes.find((i: { id: any }) => i.id === params.data.id) + ); + }); + const debouncedResize = debounce(() => { + instance.resize(); + }, 1000); + window.addEventListener('resize', debouncedResize); + } + }, []); + + return ( +
+
+
+ ); +}; + +export default Network; diff --git a/src/pages/ContentScripts/features/community-openrank-network/index.scss b/src/pages/ContentScripts/features/community-openrank-network/index.scss new file mode 100644 index 00000000..10472d06 --- /dev/null +++ b/src/pages/ContentScripts/features/community-openrank-network/index.scss @@ -0,0 +1,31 @@ +#details_table { + width: 95%; + margin: 10px; + tr:nth-child(even) { + background-color: #d6eeee; + } + th, + td { + border: 1px solid black; + text-align: center; /* 水平居中 */ + vertical-align: middle; /* 垂直居中 */ + } +} + +#details_title { + text-align: center; + font-size: 12px; +} + +.bordered { + border: 2px solid grey; +} + +#details_div { + height: 250px; +} + +.scrollit { + overflow-x: hidden; + overflow-y: auto; +} diff --git a/src/pages/ContentScripts/features/community-openrank-network/index.tsx b/src/pages/ContentScripts/features/community-openrank-network/index.tsx new file mode 100644 index 00000000..d614adf0 --- /dev/null +++ b/src/pages/ContentScripts/features/community-openrank-network/index.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { render, Container } from 'react-dom'; +import $ from 'jquery'; + +import features from '../../../../feature-manager'; +import isPerceptor from '../../../../helpers/is-perceptor'; +import { getRepoName, isPublicRepoWithMeta, isRepoRoot } from '../../../../helpers/get-repo-info'; +import { getOpenrank } from '../../../../api/community'; +import { RepoMeta, metaStore } from '../../../../api/common'; +import View from './view'; +import './index.scss'; + +const featureId = features.getFeatureID(import.meta.url); +let repoName: string; +let openrank: any; +let meta: RepoMeta; + +const getData = async () => { + meta = (await metaStore.get(repoName)) as RepoMeta; + const lastDataAvailableMonth = meta.updatedAt ? new Date(meta.updatedAt) : new Date(); + lastDataAvailableMonth.setDate(0); + + const newestMonth = + lastDataAvailableMonth.getFullYear() + '-' + (lastDataAvailableMonth.getMonth() + 1).toString().padStart(2, '0'); + openrank = await getOpenrank(repoName, '2023-09'); +}; + +const renderTo = (container: Container) => { + render(, container); +}; + +const init = async (): Promise => { + repoName = getRepoName(); + await getData(); + + // create container + const container = document.createElement('div'); + container.id = featureId; + + $('#hypercrx-perceptor-slot-community-openrank-network').append(container); + renderTo(container); +}; + +const restore = async () => { + // Clicking another repo link in one repo will trigger a turbo:visit, + // so in a restoration visit we should be careful of the current repo. + if (repoName !== getRepoName()) { + repoName = getRepoName(); + await getData(); + } + // rerender the chart or it will be empty + renderTo($(`#${featureId}`)[0]); +}; + +features.add(featureId, { + asLongAs: [isPerceptor, isPublicRepoWithMeta], + awaitDomReady: true, + init, + restore, +}); diff --git a/src/pages/ContentScripts/features/community-openrank-network/view.tsx b/src/pages/ContentScripts/features/community-openrank-network/view.tsx new file mode 100644 index 00000000..255d9974 --- /dev/null +++ b/src/pages/ContentScripts/features/community-openrank-network/view.tsx @@ -0,0 +1,71 @@ +import React, { useState, useEffect } from 'react'; + +import getGithubTheme from '../../../../helpers/get-github-theme'; +import optionsStorage, { HypercrxOptions, defaults } from '../../../../options-storage'; +import { RepoMeta } from '../../../../api/common'; +import { t } from 'i18next'; +import Network from './Network'; +import { DatePicker } from 'antd'; +import dayjs from 'dayjs'; + +interface Props { + repoName: string; + openrank: any; + meta: RepoMeta; +} + +const graphStyle = { + width: '100%', + height: '380px', +}; + +const onChange = (value: any) => { + console.log(value); // 打印选中的年月 +}; + +const View = ({ repoName, openrank, meta }: Props): JSX.Element | null => { + const [options, setOptions] = useState(defaults); + + useEffect(() => { + (async function () { + setOptions(await optionsStorage.getAll()); + })(); + }, []); + + if (!openrank) return null; + + return ( +
+
+
+ {t('component_communityOpenRankNetwork_title')} +
+ +
+
+
+
+
+ +
+
+
+
+

{t('component_communityOpenRankNetwork_description')}

+
+
+

Details

+
+
+
+
+
+
+
+
+
+
+ ); +}; + +export default View; diff --git a/src/pages/ContentScripts/index.ts b/src/pages/ContentScripts/index.ts index 197bfb02..7052a0cd 100644 --- a/src/pages/ContentScripts/index.ts +++ b/src/pages/ContentScripts/index.ts @@ -14,3 +14,4 @@ import './features/repo-networks'; import './features/developer-networks'; import './features/oss-gpt'; import './features/repo-activity-racing-bar'; +import './features/community-openrank-network'; From 3961e60eba69aff5be9bff6bd00aefab99a573df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E4=BD=91=E5=A9=89=E5=BC=98?= <2585506997@qq.com> Date: Wed, 24 Jul 2024 13:28:27 +0800 Subject: [PATCH 08/18] add DatePicker to Community OpenRank Detail Network --- src/api/community.ts | 8 +- .../community-openrank-network/Network.tsx | 211 +++++++++++------- .../community-openrank-network/index.tsx | 24 +- .../community-openrank-network/view.tsx | 31 ++- src/pages/ContentScripts/index.ts | 1 + 5 files changed, 168 insertions(+), 107 deletions(-) diff --git a/src/api/community.ts b/src/api/community.ts index bd62792c..fd2ecb6d 100644 --- a/src/api/community.ts +++ b/src/api/community.ts @@ -2,8 +2,11 @@ import request from '../helpers/request'; import { ErrorCode, OSS_XLAB_ENDPOINT } from '../constant'; export const getMetricByDate = async (repoName: string, date: string) => { + let response; try { - return await request(`${OSS_XLAB_ENDPOINT}/open_digger/github/${repoName}/project_openrank_detail/${date}.json`); + response = await request( + `${OSS_XLAB_ENDPOINT}/open_digger/github/${repoName}/project_openrank_detail/${date}.json` + ); } catch (error) { // the catched error being "404" means the metric file is not available so return a null if (error === ErrorCode.NOT_FOUND) { @@ -13,8 +16,9 @@ export const getMetricByDate = async (repoName: string, date: string) => { throw error; } } + return response; }; -export const getOpenrank = async (repo: string, date: string) => { +export const getOpenRank = async (repo: string, date: string) => { return getMetricByDate(repo, date); }; diff --git a/src/pages/ContentScripts/features/community-openrank-network/Network.tsx b/src/pages/ContentScripts/features/community-openrank-network/Network.tsx index fe335313..248d0ffa 100644 --- a/src/pages/ContentScripts/features/community-openrank-network/Network.tsx +++ b/src/pages/ContentScripts/features/community-openrank-network/Network.tsx @@ -1,9 +1,16 @@ -import React, { CSSProperties, useEffect, useRef } from 'react'; +import React, { CSSProperties, forwardRef, useEffect, useRef, ForwardedRef, useImperativeHandle } from 'react'; import * as echarts from 'echarts'; -import linearMap from '../../../../helpers/linear-map'; +import optionsStorage, { HypercrxOptions, defaults } from '../../../../options-storage'; + import { debounce } from 'lodash-es'; import getGithubTheme from '../../../../helpers/get-github-theme'; +import dayjs from 'dayjs'; +import { getOpenRank } from '../../../../api/community'; + +export interface DateControllers { + update: (newDate: string) => void; +} interface NetworkProps { /** @@ -14,10 +21,8 @@ interface NetworkProps { * `style` for graph container */ readonly style?: CSSProperties; - /** - * callback function when click node - */ - readonly focusedNodeID?: string; + + readonly focusedNodeID: string; date?: string; } @@ -67,20 +72,14 @@ const generateEchartsData = (data: any, focusedNodeID: string | undefined): any }; }; -const Network: React.FC = ({ data, style = {}, focusedNodeID, date }) => { - const divEL = useRef(null); - const graphData = generateEchartsData(data, focusedNodeID); - const option = { +const getOption = (data: any, date: string | undefined) => { + return { tooltip: { trigger: 'item', }, animation: true, animationDuration: 2000, - // title: { - // text: `OpenRank details for ${focusedNodeID} in ${date}`, - // top: 'bottom', - // left: 'right' - // }, + legend: [ { data: categories, @@ -91,8 +90,8 @@ const Network: React.FC = ({ data, style = {}, focusedNodeID, date name: 'Collaborative graph', type: 'graph', layout: 'force', - nodes: graphData.nodes, - edges: graphData.edges, + nodes: data.nodes, + edges: data.edges, categories: categories.map((c) => { return { name: c }; }), @@ -139,81 +138,121 @@ const Network: React.FC = ({ data, style = {}, focusedNodeID, date ], }, }; +}; - const clearDiv = (id: string) => { - var div = document.getElementById(id); - if (div && div.hasChildNodes()) { - var children = div.childNodes; - for (var child of children) { - div.removeChild(child); - } - } - }; +const Network = forwardRef( + ( + { data, style = {}, focusedNodeID, date }: NetworkProps, + forwardedRef: ForwardedRef + ): JSX.Element => { + const divEL = useRef(null); + let graphData = generateEchartsData(data, focusedNodeID); + let option = getOption(graphData, date); - const addRow = (table: HTMLElement | null, texts: any[]) => { - // @ts-ignore - var tr = table.insertRow(); - for (var t of texts) { - var td = tr.insertCell(); - td.appendChild(document.createTextNode(t)); - } - }; + const clearDiv = (id: string) => { + var div = document.getElementById(id); + if (div && div.hasChildNodes()) { + var children = div.childNodes; + for (var child of children) { + div.removeChild(child); + } + } + }; - const setDetails = (graph: { links: any[]; nodes: any[] }, node: { r: number; i: number; id: any }) => { - clearDiv('details_table'); - var table = document.getElementById('details_table'); - addRow(table, ['From', 'Ratio', 'Value', 'OpenRank']); - addRow(table, ['Self', node.r, node.i, (node.r * node.i).toFixed(3)]); - var other = graph.links - .filter((l) => l.t == node.id) - .map((l) => { - var source = graph.nodes.find((n) => n.id == l.s); - return [ - genName(source), - parseFloat(((1 - node.r) * l.w).toFixed(3)), - source.v, - parseFloat(((1 - node.r) * l.w * source.v).toFixed(3)), - ]; - }) - .sort((a, b) => b[3] - a[3]); - for (var r of other) { - addRow(table, r); - } - }; + const addRow = (table: HTMLElement | null, texts: any[]) => { + // @ts-ignore + var tr = table.insertRow(); + for (var t of texts) { + var td = tr.insertCell(); + td.appendChild(document.createTextNode(t)); + } + }; - useEffect(() => { - let chartDOM = divEL.current; - const instance = echarts.init(chartDOM as any); + const update = (newDate: string) => { + getOpenRank(focusedNodeID, newDate).then((openRank) => { + let chartDOM = divEL.current; + const instance = echarts.getInstanceByDom(chartDOM as any); + if (instance) { + if (openRank == null) { + instance.setOption( + { + title: { + text: `OpenRank for ${focusedNodeID} in ${newDate} is has not been generated`, + top: 'middle', + left: 'center', + }, + }, + { notMerge: true } + ); + } else { + graphData = generateEchartsData(openRank, focusedNodeID); + option = getOption(graphData, newDate); + instance.setOption(option, { notMerge: true }); + } + } + }); + }; - return () => { - instance.dispose(); + const setDetails = (graph: { links: any[]; nodes: any[] }, node: { r: number; i: number; id: any }) => { + clearDiv('details_table'); + var table = document.getElementById('details_table'); + addRow(table, ['From', 'Ratio', 'Value', 'OpenRank']); + addRow(table, ['Self', node.r, node.i, (node.r * node.i).toFixed(3)]); + var other = graph.links + .filter((l) => l.t == node.id) + .map((l) => { + var source = graph.nodes.find((n) => n.id == l.s); + return [ + genName(source), + parseFloat(((1 - node.r) * l.w).toFixed(3)), + source.v, + parseFloat(((1 - node.r) * l.w * source.v).toFixed(3)), + ]; + }) + .sort((a, b) => b[3] - a[3]); + for (var r of other) { + addRow(table, r); + } }; - }, []); - - useEffect(() => { - let chartDOM = divEL.current; - const instance = echarts.getInstanceByDom(chartDOM as any); - if (instance) { - instance.setOption(option); - instance.on('dblclick', function (params) { - setDetails( - data, - // @ts-ignore - data.nodes.find((i: { id: any }) => i.id === params.data.id) - ); - }); - const debouncedResize = debounce(() => { - instance.resize(); - }, 1000); - window.addEventListener('resize', debouncedResize); - } - }, []); - return ( -
-
-
- ); -}; + useImperativeHandle(forwardedRef, () => ({ + update, + })); + + useEffect(() => { + let chartDOM = divEL.current; + const instance = echarts.init(chartDOM as any); + + return () => { + instance.dispose(); + }; + }, []); + + useEffect(() => { + let chartDOM = divEL.current; + const instance = echarts.getInstanceByDom(chartDOM as any); + if (instance) { + instance.setOption(option); + instance.on('dblclick', function (params) { + setDetails( + data, + // @ts-ignore + data.nodes.find((i: { id: any }) => i.id === params.data.id) + ); + }); + const debouncedResize = debounce(() => { + instance.resize(); + }, 1000); + window.addEventListener('resize', debouncedResize); + } + }, []); + + return ( +
+
+
+ ); + } +); export default Network; diff --git a/src/pages/ContentScripts/features/community-openrank-network/index.tsx b/src/pages/ContentScripts/features/community-openrank-network/index.tsx index d614adf0..912fa8c6 100644 --- a/src/pages/ContentScripts/features/community-openrank-network/index.tsx +++ b/src/pages/ContentScripts/features/community-openrank-network/index.tsx @@ -5,34 +5,38 @@ import $ from 'jquery'; import features from '../../../../feature-manager'; import isPerceptor from '../../../../helpers/is-perceptor'; import { getRepoName, isPublicRepoWithMeta, isRepoRoot } from '../../../../helpers/get-repo-info'; -import { getOpenrank } from '../../../../api/community'; +import { getOpenRank } from '../../../../api/community'; import { RepoMeta, metaStore } from '../../../../api/common'; import View from './view'; import './index.scss'; +import DataNotFound from '../repo-networks/DataNotFound'; const featureId = features.getFeatureID(import.meta.url); let repoName: string; -let openrank: any; +let openRank: any; let meta: RepoMeta; const getData = async () => { meta = (await metaStore.get(repoName)) as RepoMeta; - const lastDataAvailableMonth = meta.updatedAt ? new Date(meta.updatedAt) : new Date(); - lastDataAvailableMonth.setDate(0); - - const newestMonth = - lastDataAvailableMonth.getFullYear() + '-' + (lastDataAvailableMonth.getMonth() + 1).toString().padStart(2, '0'); - openrank = await getOpenrank(repoName, '2023-09'); + // const lastDataAvailableMonth = meta.updatedAt ? new Date(meta.updatedAt) : new Date(); + // lastDataAvailableMonth.setDate(0); + // + // const newestMonth = + // lastDataAvailableMonth.getFullYear() + '-' + (lastDataAvailableMonth.getMonth() + 1).toString().padStart(2, '0'); + openRank = await getOpenRank(repoName, '2023-09'); }; const renderTo = (container: Container) => { - render(, container); + if (!openRank) { + render(, container); + return; + } + render(, container); }; const init = async (): Promise => { repoName = getRepoName(); await getData(); - // create container const container = document.createElement('div'); container.id = featureId; diff --git a/src/pages/ContentScripts/features/community-openrank-network/view.tsx b/src/pages/ContentScripts/features/community-openrank-network/view.tsx index 255d9974..6325ae7f 100644 --- a/src/pages/ContentScripts/features/community-openrank-network/view.tsx +++ b/src/pages/ContentScripts/features/community-openrank-network/view.tsx @@ -1,10 +1,9 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; -import getGithubTheme from '../../../../helpers/get-github-theme'; import optionsStorage, { HypercrxOptions, defaults } from '../../../../options-storage'; import { RepoMeta } from '../../../../api/common'; import { t } from 'i18next'; -import Network from './Network'; +import Network, { DateControllers } from './Network'; import { DatePicker } from 'antd'; import dayjs from 'dayjs'; @@ -19,12 +18,9 @@ const graphStyle = { height: '380px', }; -const onChange = (value: any) => { - console.log(value); // 打印选中的年月 -}; - const View = ({ repoName, openrank, meta }: Props): JSX.Element | null => { const [options, setOptions] = useState(defaults); + const dateControllersRef = useRef(null); useEffect(() => { (async function () { @@ -32,6 +28,11 @@ const View = ({ repoName, openrank, meta }: Props): JSX.Element | null => { })(); }, []); + const onChange = (newDate: dayjs.Dayjs) => { + let date = newDate.format('YYYY-MM'); + dateControllersRef.current?.update(date); + }; + if (!openrank) return null; return ( @@ -40,13 +41,25 @@ const View = ({ repoName, openrank, meta }: Props): JSX.Element | null => {
{t('component_communityOpenRankNetwork_title')}
- +
- +
diff --git a/src/pages/ContentScripts/index.ts b/src/pages/ContentScripts/index.ts index 7052a0cd..823b0cc9 100644 --- a/src/pages/ContentScripts/index.ts +++ b/src/pages/ContentScripts/index.ts @@ -15,3 +15,4 @@ import './features/developer-networks'; import './features/oss-gpt'; import './features/repo-activity-racing-bar'; import './features/community-openrank-network'; +import './features/community-openrank-racing-bar'; From f1effd57d5cbbf39579d4291f27c99215b94d90a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E4=BD=91=E5=A9=89=E5=BC=98?= <2585506997@qq.com> Date: Thu, 25 Jul 2024 01:02:53 +0800 Subject: [PATCH 09/18] add Community OpenRank Racing Bar --- src/locales/en/translation.json | 2 + src/locales/zh_CN/translation.json | 2 + .../RacingBar.tsx | 140 ++++++++++++++ .../community-openrank-racing-bar/data.ts | 180 ++++++++++++++++++ .../community-openrank-racing-bar/index.tsx | 60 ++++++ .../community-openrank-racing-bar/view.tsx | 98 ++++++++++ 6 files changed, 482 insertions(+) create mode 100644 src/pages/ContentScripts/features/community-openrank-racing-bar/RacingBar.tsx create mode 100644 src/pages/ContentScripts/features/community-openrank-racing-bar/data.ts create mode 100644 src/pages/ContentScripts/features/community-openrank-racing-bar/index.tsx create mode 100644 src/pages/ContentScripts/features/community-openrank-racing-bar/view.tsx diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 57de238b..8b8aae1d 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -5,6 +5,8 @@ "global_clickToshow": "Click to show", "component_communityOpenRankNetwork_title": "Community OpenRank Detail Network", "component_communityOpenRankNetwork_description": "Community OpenRank Detail Network shows the OpenRank about the project by month. Double-click a node in this network to view the details of OpenRank for that node in the table on the right. ", + "component_communityOpenRankRacingBar_title": "Community OpenRank Racing Bar", + "component_communityOpenRankRacingBar_description": "This chart shows how the OpenRank in this community evolve. ", "component_developerCollaborationNetwork_title": "Developer Collaboration Network", "component_developerCollaborationNetwork_description": "Developer Collaboration Network shows the collaboration between developers for a given time period. From this graph you can find other developers who are closet to a given developer.", "component_developerCollaborationNetwork_description_node": "Node: Developer, node size and shades of color indicate developer activity.", diff --git a/src/locales/zh_CN/translation.json b/src/locales/zh_CN/translation.json index daa32331..fc370c78 100644 --- a/src/locales/zh_CN/translation.json +++ b/src/locales/zh_CN/translation.json @@ -4,6 +4,8 @@ "global_clickToshow": "点击查看", "component_communityOpenRankNetwork_title": "社区OpenRank网络图", "component_communityOpenRankNetwork_description": "社区OpenRank网络图按月显示有关该项目的OpenRank。双击此网络中的一个节点,可以在右侧表格中查看该节点OpenRank详细信息。 ", + "component_communityOpenRankRacingBar_title": "社区OpenRank滚榜", + "component_communityOpenRankRacingBar_description": "社区OpenRank滚榜展示了社区中OpenRank的演化过程。", "component_developerCollaborationNetwork_title": "开发者协作网络图", "component_developerCollaborationNetwork_description": "开发者协作网络图展示了在给定的时间段内,开发者与开发者之间的协作关系, 用于开发者关系的追踪与挖掘。从该网络图中,可以找出与该开发者联系较为紧密的其他开发者。", "component_developerCollaborationNetwork_description_node": "节点:一个节点表示开发者,节点大小与颜色的深浅表示开发者活跃度的大小。", diff --git a/src/pages/ContentScripts/features/community-openrank-racing-bar/RacingBar.tsx b/src/pages/ContentScripts/features/community-openrank-racing-bar/RacingBar.tsx new file mode 100644 index 00000000..d7f21a4f --- /dev/null +++ b/src/pages/ContentScripts/features/community-openrank-racing-bar/RacingBar.tsx @@ -0,0 +1,140 @@ +import { CommunityOpenRankDetails, getOption, countLongTermContributors, DEFAULT_FREQUENCY } from './data'; +import sleep from '../../../../helpers/sleep'; + +import React, { useEffect, 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 { + speed: number; + data: CommunityOpenRankDetails; + setPlaying: (playing: boolean) => void; +} + +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 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); + } + }; + + 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(); + }; + + const pause = () => { + setPlaying(false); + + 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 () => { + const instance = echarts.init(divEL.current!); + updateMonth(instance, months[monthIndexRef.current], false); + })(); + + return () => { + if (timerRef.current) { + clearTimeout(timerRef.current); + } + const instance = echarts.getInstanceByDom(divEL.current!); + if (instance && !instance.isDisposed()) { + instance.dispose(); + } + }; + }, []); + + return ( +
+
+
+ ); + } +); + +export default RacingBar; diff --git a/src/pages/ContentScripts/features/community-openrank-racing-bar/data.ts b/src/pages/ContentScripts/features/community-openrank-racing-bar/data.ts new file mode 100644 index 00000000..3e838e64 --- /dev/null +++ b/src/pages/ContentScripts/features/community-openrank-racing-bar/data.ts @@ -0,0 +1,180 @@ +import getGithubTheme from '../../../../helpers/get-github-theme'; + +import type { BarSeriesOption, EChartsOption } from 'echarts'; +import { orderBy, take } from 'lodash-es'; + +const theme = getGithubTheme(); +const DARK_TEXT_COLOR = 'rgba(230, 237, 243, 0.9)'; + +export interface CommunityOpenRankDetails { + // e.g. 2020-05: [["frank-zsy", 4.69], ["heming6666", 3.46], ["menbotics[bot]", 2]] + [key: string]: [string, string, number][]; +} + +/** + * Filter and extract monthly data from the given data structure, which includes various date formats such as "yyyy", "yyyy-Qq", and "yyyy-mm". + * This function extracts and returns only the monthly data in the "yyyy-mm" format. + * + * @returns CommunityOpenRankDetails + * @param data + */ +export function getMonthlyData(data: CommunityOpenRankDetails) { + const monthlyData: CommunityOpenRankDetails = {}; + + for (const key in data) { + // Check if the key matches the yyyy-mm format (e.g., "2020-05") + if (/^\d{4}-\d{2}$/.test(key)) { + monthlyData[key] = data[key]; + } + } + return monthlyData; +} + +/** + * Count the number of unique contributors in the data + * @returns [number of long term contributors, contributors' names] + */ +export const countLongTermContributors = (data: CommunityOpenRankDetails): [number, string[]] => { + const map = new Map(); + Object.keys(data).forEach((month) => { + data[month].forEach((item) => { + if (map.has(item[0])) { + map.set(item[0], map.get(item[0])! + 1); + } else { + map.set(item[0], 0); + } + }); + }); + let count = 0; + map.forEach((value) => { + // only count map who have contributed more than 3 months + if (value >= 3) { + count++; + } + }); + return [count, [...map.keys()]]; +}; + +export const DEFAULT_FREQUENCY = 2000; + +/** + * get the echarts option with the given data, month and speed. + */ +export const getOption = async ( + data: CommunityOpenRankDetails, + month: string, + speed: number, + maxBars: number, + enableAnimation: boolean +): Promise => { + const updateFrequency = DEFAULT_FREQUENCY / speed; + const rich: any = {}; + const sortedData = orderBy(data[month], (item) => item[1], 'desc'); + const topData = take(sortedData, maxBars); + const colorMap = new Map([ + ['r', '#72a8d6'], + ['i', '#98F8DD'], + ['p', '#F2FF7F'], + ['u', '#f8a8ab'], + ]); + const barData: BarSeriesOption['data'] = await Promise.all( + topData.map(async (item) => { + let color = colorMap.get(item[1]); + return { + value: [item[0], item[2]], + itemStyle: { + color: { + type: 'linear', + x: 0, + y: 0, + x2: 1, + y2: 0, + colorStops: [ + { + offset: 0, + color: 'white', + }, + { + offset: 0.5, + color: color, + }, + ], + global: false, + }, + }, + }; + }) + ); + + return { + grid: { + top: 10, + bottom: 30, + left: 160, + right: 50, + }, + xAxis: { + max: 'dataMax', + axisLabel: { + show: true, + color: theme === 'light' ? undefined : DARK_TEXT_COLOR, + }, + }, + yAxis: { + type: 'category', + inverse: true, + max: maxBars, + axisLabel: { + show: true, + color: theme === 'light' ? undefined : DARK_TEXT_COLOR, + fontSize: 14, + rich, + }, + axisTick: { + show: false, + }, + animationDuration: 0, + animationDurationUpdate: enableAnimation ? 200 : 0, + }, + series: [ + { + realtimeSort: true, + seriesLayoutBy: 'column', + type: 'bar', + data: barData, + encode: { + x: 1, + y: 0, + }, + label: { + show: true, + precision: 1, + position: 'right', + valueAnimation: true, + fontFamily: 'monospace', + color: theme === 'light' ? undefined : DARK_TEXT_COLOR, + }, + }, + ], + // Disable init animation. + animationDuration: 0, + animationDurationUpdate: enableAnimation ? updateFrequency : 0, + animationEasing: 'linear', + animationEasingUpdate: 'linear', + graphic: { + elements: [ + { + type: 'text', + right: 60, + bottom: 60, + style: { + text: month, + font: 'bolder 60px monospace', + fill: theme === 'light' ? 'rgba(100, 100, 100, 0.3)' : DARK_TEXT_COLOR, + }, + z: 100, + }, + ], + }, + }; +}; diff --git a/src/pages/ContentScripts/features/community-openrank-racing-bar/index.tsx b/src/pages/ContentScripts/features/community-openrank-racing-bar/index.tsx new file mode 100644 index 00000000..49f2acab --- /dev/null +++ b/src/pages/ContentScripts/features/community-openrank-racing-bar/index.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { Container, render } from 'react-dom'; +import $ from 'jquery'; + +import features from '../../../../feature-manager'; +import isPerceptor from '../../../../helpers/is-perceptor'; +import { getRepoName, isPublicRepoWithMeta } from '../../../../helpers/get-repo-info'; +import { getOpenRank } from '../../../../api/community'; +import { getActivityDetails } from '../../../../api/repo'; +import View from './view'; +import DataNotFound from '../repo-networks/DataNotFound'; +import { CommunityOpenRankDetails } from './data'; +import { JsonObject } from 'type-fest'; + +const featureId = features.getFeatureID(import.meta.url); +let repoName: string; +let communityOpenRankDetails: CommunityOpenRankDetails = {}; + +const getData = async () => { + repoName = getRepoName(); + for (let year = 2020; year <= 2024; year++) { + for (let month = 1; month <= 12; month++) { + let date = year.toString() + '-' + String(month).padStart(2, '0'); + const rawData = await getOpenRank(repoName, date); + if (rawData !== null) { + communityOpenRankDetails[date] = rawData.nodes.map((node: any) => [node.n, node.c, node.v]); + } + } + } +}; + +const renderTo = (container: Container) => { + render(, container); +}; + +const init = async (): Promise => { + await getData(); + const container = document.createElement('div'); + container.id = featureId; + + $('#hypercrx-perceptor-slot-community-openrank-racing-bar').append(container); + renderTo(container); +}; + +const restore = async () => { + // Clicking another repo link in one repo will trigger a turbo:visit, + // so in a restoration visit we should be careful of the current repo. + if (repoName !== getRepoName()) { + repoName = getRepoName(); + } + // rerender the chart or it will be empty + renderTo($(`#${featureId}`)[0]); +}; + +features.add(featureId, { + asLongAs: [isPerceptor, isPublicRepoWithMeta], + awaitDomReady: false, + init, + restore, +}); diff --git a/src/pages/ContentScripts/features/community-openrank-racing-bar/view.tsx b/src/pages/ContentScripts/features/community-openrank-racing-bar/view.tsx new file mode 100644 index 00000000..3c2ff5f9 --- /dev/null +++ b/src/pages/ContentScripts/features/community-openrank-racing-bar/view.tsx @@ -0,0 +1,98 @@ +import optionsStorage, { HypercrxOptions, defaults } from '../../../../options-storage'; +import RacingBar, { MediaControlers } from './RacingBar'; +import { CommunityOpenRankDetails, getMonthlyData } from './data'; +import { PlayerButton } from '../repo-activity-racing-bar/PlayerButton'; +import { SpeedController } from '../repo-activity-racing-bar/SpeedController'; + +import React, { useState, useEffect, useRef } from 'react'; +import { Space } from 'antd'; +import { PlayCircleFilled, StepBackwardFilled, StepForwardFilled, PauseCircleFilled } from '@ant-design/icons'; +import { useTranslation } from 'react-i18next'; +import '../../../../helpers/i18n'; +interface Props { + currentRepo: string; + communityOpenRankDetails: CommunityOpenRankDetails; +} + +const View = ({ currentRepo, communityOpenRankDetails }: Props): JSX.Element => { + const [options, setOptions] = useState(defaults); + const [speed, setSpeed] = useState(1); + const [playing, setPlaying] = useState(false); + const mediaControlersRef = useRef(null); + const { t, i18n } = useTranslation(); + useEffect(() => { + (async function () { + setOptions(await optionsStorage.getAll()); + i18n.changeLanguage(options.locale); + })(); + }, [options.locale]); + + return ( +
+
+
+ {t('component_communityOpenRankRacingBar_title')} +
+ + {/* speed control */} + { + setSpeed(speed); + }} + /> + + {/* 3 buttons */} + + {/* last month | earliest month */} + } + onClick={mediaControlersRef.current?.previous} + onLongPress={mediaControlersRef.current?.earliest} + /> + {/* play | pause */} + : } + onClick={() => { + if (playing) { + mediaControlersRef.current?.pause(); + } else { + mediaControlersRef.current?.play(); + } + }} + /> + {/* next month | latest month */} + } + onClick={mediaControlersRef.current?.next} + onLongPress={mediaControlersRef.current?.latest} + /> + + +
+
+
+
+
+ +
+
+
+
+

{t('component_communityOpenRankRacingBar_description')}

+
+
+
+
+
+ ); +}; + +export default View; From cf334c3143430d9154e8d39eda34b9d076e1d9a8 Mon Sep 17 00:00:00 2001 From: Fiveneves <75442734+Fiveneves@users.noreply.github.com> Date: Thu, 25 Jul 2024 01:12:32 +0800 Subject: [PATCH 10/18] Delete src/pages/ContentScripts/features/colorful-calendar directory --- .../features/colorful-calendar/index.scss | 19 ---- .../features/colorful-calendar/index.tsx | 97 ------------------- 2 files changed, 116 deletions(-) delete mode 100644 src/pages/ContentScripts/features/colorful-calendar/index.scss delete mode 100644 src/pages/ContentScripts/features/colorful-calendar/index.tsx diff --git a/src/pages/ContentScripts/features/colorful-calendar/index.scss b/src/pages/ContentScripts/features/colorful-calendar/index.scss deleted file mode 100644 index daad3949..00000000 --- a/src/pages/ContentScripts/features/colorful-calendar/index.scss +++ /dev/null @@ -1,19 +0,0 @@ -.ant-color-picker-trigger { - min-width: 10px !important; - padding: 0 !important; - margin-right: 4px; - border: none !important; -} - -.ant-color-picker-color-block { - width: 10px !important; - min-width: 10px !important; - height: 10px !important; -} - -.ant-color-picker-color-block-inner { - width: 10px !important; - min-width: 10px !important; - height: 10px !important; - border-radius: 3px !important; -} diff --git a/src/pages/ContentScripts/features/colorful-calendar/index.tsx b/src/pages/ContentScripts/features/colorful-calendar/index.tsx deleted file mode 100644 index 50707821..00000000 --- a/src/pages/ContentScripts/features/colorful-calendar/index.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import features from '../../../../feature-manager'; -import waitFor from '../../../../helpers/wait-for'; - -import React from 'react'; -import { render } from 'react-dom'; -import { ColorPicker } from 'antd'; -import $ from 'jquery'; -import * as pageDetect from 'github-url-detection'; - -import './index.scss'; // 需要引入自定义的样式来覆盖antd ColorPicker的默认样式,后面展开说明 - -const featureId = features.getFeatureID(import.meta.url); - -// const CALENDAR_LEVEL_COLORS = [ -// '#ebedf0', -// '#ffedf9', -// '#ffc3eb', -// '#ff3ebf', -// '#c70085', -// ]; -// -// const changeLevelColor = (level: number, color: string) => { -// const root = document.documentElement; -// if (level === 0) { -// root.style.setProperty(`--color-calendar-graph-day-bg`, color); -// } else { -// root.style.setProperty(`--color-calendar-graph-day-L${level}-bg`, color); -// } -// }; - -let colors = ['#ebedf0', '#ffedf9', '#ffc3eb', '#ff3ebf', '#c70085']; - -const changeLevelColor = async (level: number, color: string) => { - const root = document.documentElement; - if (level === 0) { - root.style.setProperty(`--color-calendar-graph-day-bg`, color); - } else { - root.style.setProperty(`--color-calendar-graph-day-L${level}-bg`, color); - } - // Save to storage - const newColors = [...colors]; - newColors[level] = color; - await chrome.storage.local.set({ - calendar_level_colors: newColors, - }); -}; - -const replaceLegendToColorPicker = async ( - level: number, - defaultColor: string -) => { - const legendSelector = `#contribution-graph-legend-level-${level}`; // 选择器selector是用于定位DOM元素的字符串 - await waitFor(() => $(legendSelector).length > 0); // init函数运行的时候,页面中某些元素不一定已经加载完毕,经过测试,日历图加载时机比较靠后,因此需要waitFor一下,不然后面的操作都是无用的 - const $legend = $(legendSelector); - const container = $('
'); - render( - changeLevelColor(level, hex)} - />, // 选择新颜色后会调用changeLevelColor改变格子颜色 - container[0] - ); // 将React组件渲染为真实的DOM元素 - $legend.replaceWith(container); // 使用jQuery的replaceWith方法将图例格子替换为ColorPicker -}; - -// const init = async (): Promise => { -// for (let i = 0; i < CALENDAR_LEVEL_COLORS.length; i++) { -// changeLevelColor(i, CALENDAR_LEVEL_COLORS[i]); // 初始化时就按照给定的颜色改变日历格子的颜色 -// await replaceLegendToColorPicker(i, CALENDAR_LEVEL_COLORS[i]); -// } -// }; - -const init = async (): Promise => { - // Load colors from storage - colors = - (await chrome.storage.local.get('calendar_level_colors'))[ - // (await localStorage.get('calendar_level_colors'))[ - 'calendar_level_colors' - ] || colors; - - for (let i = 0; i < colors.length; i++) { - changeLevelColor(i, colors[i]); - replaceLegendToColorPicker(i, colors[i]); - } -}; - -const restore = async () => { - console.log('restore colorful-calendar'); -}; - -features.add(featureId, { - asLongAs: [pageDetect.isUserProfile], - awaitDomReady: false, - init, - restore, -}); From 968fd6e4562b22bc82b3ebd523782eb4afd2f64d Mon Sep 17 00:00:00 2001 From: Fiveneves <75442734+Fiveneves@users.noreply.github.com> Date: Thu, 25 Jul 2024 01:57:28 +0800 Subject: [PATCH 11/18] remove colorful calendar from index.ts --- src/pages/ContentScripts/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/ContentScripts/index.ts b/src/pages/ContentScripts/index.ts index 21f86843..9531d43c 100644 --- a/src/pages/ContentScripts/index.ts +++ b/src/pages/ContentScripts/index.ts @@ -1,6 +1,5 @@ import './index.scss'; -import './features/colorful-calendar'; import './features/repo-activity-openrank-trends'; import './features/developer-activity-openrank-trends'; import './features/repo-header-labels'; From 124ff58defc8847a2f2561b540c7c696379a85e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E4=BD=91=E5=A9=89=E5=BC=98?= <2585506997@qq.com> Date: Thu, 25 Jul 2024 14:27:18 +0800 Subject: [PATCH 12/18] Fix a Bug in sorting data --- .../features/community-openrank-racing-bar/data.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ContentScripts/features/community-openrank-racing-bar/data.ts b/src/pages/ContentScripts/features/community-openrank-racing-bar/data.ts index 3e838e64..e7d4a335 100644 --- a/src/pages/ContentScripts/features/community-openrank-racing-bar/data.ts +++ b/src/pages/ContentScripts/features/community-openrank-racing-bar/data.ts @@ -69,7 +69,7 @@ export const getOption = async ( ): Promise => { const updateFrequency = DEFAULT_FREQUENCY / speed; const rich: any = {}; - const sortedData = orderBy(data[month], (item) => item[1], 'desc'); + const sortedData = orderBy(data[month], (item) => item[2], 'desc'); const topData = take(sortedData, maxBars); const colorMap = new Map([ ['r', '#72a8d6'], From 0e01d645fb8ab3df3d5c5a1d10a0e1988fe35d28 Mon Sep 17 00:00:00 2001 From: reset0514 <146703959+reset0514@users.noreply.github.com> Date: Thu, 25 Jul 2024 23:34:42 +0800 Subject: [PATCH 13/18] Update index.tsx --- .../features/community-openrank-racing-bar/index.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/pages/ContentScripts/features/community-openrank-racing-bar/index.tsx b/src/pages/ContentScripts/features/community-openrank-racing-bar/index.tsx index 49f2acab..5d216583 100644 --- a/src/pages/ContentScripts/features/community-openrank-racing-bar/index.tsx +++ b/src/pages/ContentScripts/features/community-openrank-racing-bar/index.tsx @@ -28,7 +28,18 @@ const getData = async () => { } } }; +const filterNodesByType = (data: CommunityOpenRankDetails, type: string): CommunityOpenRankDetails => { + const filteredData: CommunityOpenRankDetails = {}; + for (const [date, nodes] of Object.entries(data)) { + filteredData[date] = nodes.filter(([_, c]) => c === type); + } + + return filteredData; +}; +const filterByI = filterNodesByType(communityOpenRankDetails, 'i'); +const filterByP = filterNodesByType(communityOpenRankDetails, 'p'); +const filterByU = filterNodesByType(communityOpenRankDetails, 'u'); const renderTo = (container: Container) => { render(, container); }; From 99543c44f0b42ed5f7d1aaad7ed5725d64cd05e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E4=BD=91=E5=A9=89=E5=BC=98?= <2585506997@qq.com> Date: Fri, 26 Jul 2024 02:08:54 +0800 Subject: [PATCH 14/18] delete Chinese comments --- .../features/community-openrank-network/index.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/ContentScripts/features/community-openrank-network/index.scss b/src/pages/ContentScripts/features/community-openrank-network/index.scss index 10472d06..efb35b0b 100644 --- a/src/pages/ContentScripts/features/community-openrank-network/index.scss +++ b/src/pages/ContentScripts/features/community-openrank-network/index.scss @@ -7,8 +7,8 @@ th, td { border: 1px solid black; - text-align: center; /* 水平居中 */ - vertical-align: middle; /* 垂直居中 */ + text-align: center; + vertical-align: middle; } } From b8b220f876c7663573dfc69508be777a77b7b5ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E4=BD=91=E5=A9=89=E5=BC=98?= <2585506997@qq.com> Date: Fri, 26 Jul 2024 02:13:19 +0800 Subject: [PATCH 15/18] delete code that has been commented out --- .../features/community-openrank-network/index.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/pages/ContentScripts/features/community-openrank-network/index.tsx b/src/pages/ContentScripts/features/community-openrank-network/index.tsx index 912fa8c6..7f88c309 100644 --- a/src/pages/ContentScripts/features/community-openrank-network/index.tsx +++ b/src/pages/ContentScripts/features/community-openrank-network/index.tsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import features from '../../../../feature-manager'; import isPerceptor from '../../../../helpers/is-perceptor'; -import { getRepoName, isPublicRepoWithMeta, isRepoRoot } from '../../../../helpers/get-repo-info'; +import { getRepoName, isPublicRepoWithMeta } from '../../../../helpers/get-repo-info'; import { getOpenRank } from '../../../../api/community'; import { RepoMeta, metaStore } from '../../../../api/common'; import View from './view'; @@ -18,11 +18,6 @@ let meta: RepoMeta; const getData = async () => { meta = (await metaStore.get(repoName)) as RepoMeta; - // const lastDataAvailableMonth = meta.updatedAt ? new Date(meta.updatedAt) : new Date(); - // lastDataAvailableMonth.setDate(0); - // - // const newestMonth = - // lastDataAvailableMonth.getFullYear() + '-' + (lastDataAvailableMonth.getMonth() + 1).toString().padStart(2, '0'); openRank = await getOpenRank(repoName, '2023-09'); }; From 3c5113edf6dd0b241fed6b12c0ee7c19c0950f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E4=BD=91=E5=A9=89=E5=BC=98?= <2585506997@qq.com> Date: Fri, 26 Jul 2024 02:17:02 +0800 Subject: [PATCH 16/18] delete imported items are not used --- .../features/community-openrank-network/Network.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/pages/ContentScripts/features/community-openrank-network/Network.tsx b/src/pages/ContentScripts/features/community-openrank-network/Network.tsx index 248d0ffa..b9430c89 100644 --- a/src/pages/ContentScripts/features/community-openrank-network/Network.tsx +++ b/src/pages/ContentScripts/features/community-openrank-network/Network.tsx @@ -1,11 +1,8 @@ import React, { CSSProperties, forwardRef, useEffect, useRef, ForwardedRef, useImperativeHandle } from 'react'; import * as echarts from 'echarts'; -import optionsStorage, { HypercrxOptions, defaults } from '../../../../options-storage'; - import { debounce } from 'lodash-es'; import getGithubTheme from '../../../../helpers/get-github-theme'; -import dayjs from 'dayjs'; import { getOpenRank } from '../../../../api/community'; export interface DateControllers { @@ -102,10 +99,7 @@ const getOption = (data: any, date: string | undefined) => { show: true, }, force: { - // initLayout: 'circular', - // gravity: 0.1, repulsion: 300, - // edgeLength: [50, 100], // Disable the iteration animation of layout layoutAnimation: false, }, From c69e4d24eabc4606f6bd36d54d51ef800af1e8e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E4=BD=91=E5=A9=89=E5=BC=98?= <2585506997@qq.com> Date: Fri, 26 Jul 2024 02:18:29 +0800 Subject: [PATCH 17/18] add rsuite dependency --- package.json | 1 + yarn.lock | 140 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 137 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 6c75d6df..07c6b396 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "react-hot-loader": "^4.13.0", "react-i18next": "^14.1.2", "react-modal": "3.15.1", + "rsuite": "^5.67.0", "strip-indent": "^4.0.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 20b5a462..85c4261c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1024,6 +1024,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.12.1", "@babel/runtime@^7.20.1": + version "7.24.8" + resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.24.8.tgz#5d958c3827b13cc6d05e038c07fb2e5e3420d82e" + integrity sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15", "@babel/template@^7.24.0": version "7.24.0" resolved "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" @@ -1127,6 +1134,11 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@juggle/resize-observer@^3.3.1", "@juggle/resize-observer@^3.4.0": + version "3.4.0" + resolved "https://registry.npmmirror.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" + integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA== + "@leichtgewicht/ip-codec@^2.0.1": version "2.0.5" resolved "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" @@ -1231,6 +1243,22 @@ rc-resize-observer "^1.3.1" rc-util "^5.38.0" +"@rsuite/icon-font@^4.0.0": + version "4.0.0" + resolved "https://registry.npmmirror.com/@rsuite/icon-font/-/icon-font-4.0.0.tgz#c4a772af5020bb3bbf74761879f80da23e914123" + integrity sha512-rZTgpTH3H3HLczCA2rnkWfoMKm0ZXoRzsrkVujfP/FfslnKUMvO6w56pa8pCvhWGpNEPUsLS2ULnFGpTEcup/Q== + +"@rsuite/icons@^1.0.0", "@rsuite/icons@^1.0.2": + version "1.0.3" + resolved "https://registry.npmmirror.com/@rsuite/icons/-/icons-1.0.3.tgz#4cc9dc5732882fb56d56ff88152487846754323e" + integrity sha512-qkjYFn1v5YV9eH57Q4AJ8CwsQYfILun2wdoxhQg5+xYxkIu6UyF8vTMmpOzLvcybTE7D8STm4dH7vhpyhPOC7g== + dependencies: + "@babel/runtime" "^7.12.1" + "@rsuite/icon-font" "^4.0.0" + classnames "^2.2.5" + insert-css "^2.0.0" + lodash "^4.17.20" + "@sindresorhus/is@^4.0.0": version "4.6.0" resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" @@ -1443,6 +1471,11 @@ resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz#d774355e41f372d5350a4d0714abb48194a489c3" integrity sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA== +"@types/lodash@^4.14.184": + version "4.17.7" + resolved "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.7.tgz#2f776bcb53adc9e13b2c0dfd493dfcbd7de43612" + integrity sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA== + "@types/mime@^1": version "1.3.5" resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" @@ -1467,7 +1500,7 @@ dependencies: undici-types "~5.26.4" -"@types/prop-types@*": +"@types/prop-types@*", "@types/prop-types@^15.7.5": version "15.7.12" resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== @@ -1513,6 +1546,13 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" +"@types/react-window@^1.8.5": + version "1.8.8" + resolved "https://registry.npmmirror.com/@types/react-window/-/react-window-1.8.8.tgz#c20645414d142364fbe735818e1c1e0a145696e3" + integrity sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@17.0.2", "@types/react@^17": version "17.0.2" resolved "https://registry.npmjs.org/@types/react/-/react-17.0.2.tgz#3de24c4efef902dd9795a49c75f760cbe4f7a5a8" @@ -2585,7 +2625,7 @@ data-uri-to-buffer@0.0.3: resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-0.0.3.tgz#18ae979a6a0ca994b0625853916d2662bbae0b1a" integrity sha512-Cp+jOa8QJef5nXS5hU7M1DWzXPEIoVR3kbV0dQuVGwROZg8bGf1DcCnkmajBTnvghTtSNMUdRrPjgaT6ZQucbw== -date-fns@^2.11.1: +date-fns@^2.11.1, date-fns@^2.29.3: version "2.30.0" resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== @@ -2721,6 +2761,13 @@ dom-converter@^0.2.0: dependencies: utila "~0.4" +dom-lib@^3.1.3, dom-lib@^3.3.1: + version "3.3.1" + resolved "https://registry.npmmirror.com/dom-lib/-/dom-lib-3.3.1.tgz#3a6f097d57a22b0a1c9cc08bbe5034bad5df6906" + integrity sha512-N2mpo8qQmB9wIMZJVjER+BSh4GJiZZ7S6EjnMtyETcXo90hpITUDXpUhqOcfXZ2ZefytuYYKTZMp3CGR2X+tDA== + dependencies: + "@babel/runtime" "^7.20.0" + dom-loaded@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/dom-loaded/-/dom-loaded-3.0.0.tgz#0164b9bf60ac95ea00ff0d2c8abfe68fb7769759" @@ -3225,6 +3272,13 @@ get-stream@^6.0.0, get-stream@^6.0.1: resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-value@^3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/get-value/-/get-value-3.0.1.tgz#5efd2a157f1d6a516d7524e124ac52d0a39ef5a8" + integrity sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA== + dependencies: + isobject "^3.0.1" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -3648,6 +3702,11 @@ inherits@2.0.3: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== +insert-css@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/insert-css/-/insert-css-2.0.0.tgz#eb5d1097b7542f4c79ea3060d3aee07d053880f4" + integrity sha512-xGq5ISgcUP5cvGkS2MMFLtPDBtrtQPSFfC6gA6U8wHKqfjTIMZLZNxOItQnoSjdOzlXOLU/yD32RKC4SvjNbtA== + interpret@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" @@ -3740,6 +3799,11 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-primitive@^3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/is-primitive/-/is-primitive-3.0.1.tgz#98c4db1abff185485a657fc2905052b940524d05" + integrity sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w== + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -3997,7 +4061,7 @@ lodash.union@^4.6.0: resolved "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw== -lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.21: +lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.21: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4095,6 +4159,11 @@ memfs@^3.4.3: dependencies: fs-monkey "^1.0.4" +"memoize-one@>=3.1.1 <6": + version "5.2.1" + resolved "https://registry.npmmirror.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -4666,7 +4735,7 @@ process@^0.11.10: resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== -prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.7.2: +prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -5199,6 +5268,19 @@ react-redux@^7.2.4: prop-types "^15.7.2" react-is "^17.0.2" +react-use-set@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/react-use-set/-/react-use-set-1.0.0.tgz#2b8b442c6e8c77a907534dcc665d54c3f7b3c841" + integrity sha512-6BBbOcWc/tOKuwd9gDtdunvOr/g40S0SkCBYvrSJvpI0upzNlHmLoeDvylnoP8PrjQXItClAFxseVGGhEkk7kw== + +react-window@^1.8.8: + version "1.8.10" + resolved "https://registry.npmmirror.com/react-window/-/react-window-1.8.10.tgz#9e6b08548316814b443f7002b1cf8fd3a1bdde03" + integrity sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg== + dependencies: + "@babel/runtime" "^7.0.0" + memoize-one ">=3.1.1 <6" + react@^17.0.2: version "17.0.2" resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -5421,6 +5503,40 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" +rsuite-table@^5.18.3: + version "5.18.3" + resolved "https://registry.npmmirror.com/rsuite-table/-/rsuite-table-5.18.3.tgz#08d3a6cd4cd979b1100e015a2f5dec4d37bfbfd1" + integrity sha512-Rua79XndYY+UdCUpBuH1Ew5qa54y6zLZ0RNRnudKgamksrV1j+rUhcCsA03a5ZY+b8DXTwct4V/Q6K9q/cJT5w== + dependencies: + "@babel/runtime" "^7.12.5" + "@juggle/resize-observer" "^3.3.1" + "@rsuite/icons" "^1.0.0" + classnames "^2.3.1" + dom-lib "^3.1.3" + lodash "^4.17.21" + react-is "^17.0.2" + +rsuite@^5.67.0: + version "5.67.0" + resolved "https://registry.npmmirror.com/rsuite/-/rsuite-5.67.0.tgz#a19090aff86a2c282166d858e42ac74d9ec39c01" + integrity sha512-dpuv5RzLwNRC63b1jHEac4nQkXO6sFStN9/0suq/3codGX4Glx6TtafucKjo+0iXbi4pOahbjQD/sGyWA0BO8A== + dependencies: + "@babel/runtime" "^7.20.1" + "@juggle/resize-observer" "^3.4.0" + "@rsuite/icons" "^1.0.2" + "@types/lodash" "^4.14.184" + "@types/prop-types" "^15.7.5" + "@types/react-window" "^1.8.5" + classnames "^2.3.1" + date-fns "^2.29.3" + dom-lib "^3.3.1" + lodash "^4.17.11" + prop-types "^15.8.1" + react-use-set "^1.0.0" + react-window "^1.8.8" + rsuite-table "^5.18.3" + schema-typed "^2.2.2" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -5468,6 +5584,14 @@ scheduler@^0.20.2: loose-envify "^1.1.0" object-assign "^4.1.1" +schema-typed@^2.2.2: + version "2.2.2" + resolved "https://registry.npmmirror.com/schema-typed/-/schema-typed-2.2.2.tgz#88ea6ca9f26557a4846494ab2be6ca5f23019b77" + integrity sha512-hRmqKr5V6UyhmZ0FixRVetgxvudRPjDynVZZRNq6t4EZHii7U33vmqd9uap3s4aqBcDg1JtubMNvCEmsZTpm3Q== + dependencies: + get-value "^3.0.1" + set-value "^4.1.0" + schema-utils@^2.6.5: version "2.7.1" resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" @@ -5596,6 +5720,14 @@ set-function-length@^1.2.1: gopd "^1.0.1" has-property-descriptors "^1.0.2" +set-value@^4.1.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/set-value/-/set-value-4.1.0.tgz#aa433662d87081b75ad88a4743bd450f044e7d09" + integrity sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw== + dependencies: + is-plain-object "^2.0.4" + is-primitive "^3.0.1" + setprototypeof@1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" From 1a4e558fb1e80daf40ad67581efe98564bc424eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E4=BD=91=E5=A9=89=E5=BC=98?= <2585506997@qq.com> Date: Fri, 26 Jul 2024 02:34:10 +0800 Subject: [PATCH 18/18] add a selectPicker to the CommunityOpenRankRacingBar feature to choose different categories for comparison --- .../RacingBar.tsx | 78 +++++++++++++------ .../community-openrank-racing-bar/data.ts | 8 +- .../community-openrank-racing-bar/index.tsx | 16 +--- .../community-openrank-racing-bar/view.tsx | 42 +++++++--- 4 files changed, 92 insertions(+), 52 deletions(-) diff --git a/src/pages/ContentScripts/features/community-openrank-racing-bar/RacingBar.tsx b/src/pages/ContentScripts/features/community-openrank-racing-bar/RacingBar.tsx index d7f21a4f..cec4d350 100644 --- a/src/pages/ContentScripts/features/community-openrank-racing-bar/RacingBar.tsx +++ b/src/pages/ContentScripts/features/community-openrank-racing-bar/RacingBar.tsx @@ -1,18 +1,18 @@ -import { CommunityOpenRankDetails, getOption, countLongTermContributors, DEFAULT_FREQUENCY } from './data'; +import { CommunityOpenRankDetails, getOption, countLongTermItems, DEFAULT_FREQUENCY } from './data'; import sleep from '../../../../helpers/sleep'; import React, { useEffect, useRef, forwardRef, useImperativeHandle, ForwardedRef } from 'react'; -import { Spin } from 'antd'; import * as echarts from 'echarts'; import type { EChartsType } from 'echarts'; -export interface MediaControlers { +export interface MediaControllers { play: () => void; pause: () => void; next: () => void; previous: () => void; latest: () => void; earliest: () => void; + updateType: (type: string) => void; } interface RacingBarProps { @@ -22,21 +22,23 @@ interface RacingBarProps { } const RacingBar = forwardRef( - ({ speed, data, setPlaying }: RacingBarProps, forwardedRef: ForwardedRef): JSX.Element => { + ({ speed, data, setPlaying }: RacingBarProps, forwardedRef: ForwardedRef): JSX.Element => { const divEL = useRef(null); const timerRef = useRef(); const speedRef = useRef(speed); + const openRankRef = useRef(data); speedRef.current = speed; - const months = Object.keys(data); - const monthIndexRef = useRef(months.length - 1); + const monthsRef = useRef(Object.keys(openRankRef.current)); + const monthIndexRef = useRef(monthsRef.current.length - 1); - const [longTermContributorsCount, contributors] = countLongTermContributors(data); - const maxBars = longTermContributorsCount >= 20 ? 20 : 10; - const height = longTermContributorsCount >= 20 ? 600 : 300; + let longTermItemsCount = countLongTermItems(openRankRef.current); + + const maxBarsRef = useRef(longTermItemsCount >= 20 ? 20 : 10); + const heightRef = useRef(longTermItemsCount >= 20 ? 600 : 300); const updateMonth = async (instance: EChartsType, month: string, enableAnimation: boolean) => { - const option = await getOption(data, month, speedRef.current, maxBars, enableAnimation); + const option = await getOption(openRankRef.current, month, speedRef.current, maxBarsRef.current, enableAnimation); instance.setOption(option); }; @@ -44,8 +46,8 @@ const RacingBar = forwardRef( const nextMonth = async () => { monthIndexRef.current++; const instance = echarts.getInstanceByDom(divEL.current!)!; - updateMonth(instance, months[monthIndexRef.current], true); - if (monthIndexRef.current < months.length - 1) { + updateMonth(instance, monthsRef.current[monthIndexRef.current], true); + if (monthIndexRef.current < monthsRef.current.length - 1) { timerRef.current = setTimeout(nextMonth, DEFAULT_FREQUENCY / speedRef.current); } else { setTimeout(() => { @@ -56,7 +58,7 @@ const RacingBar = forwardRef( setPlaying(true); // if the current month is the latest month, go to the beginning - if (monthIndexRef.current === months.length - 1) { + if (monthIndexRef.current === monthsRef.current.length - 1) { earliest(); await sleep(DEFAULT_FREQUENCY / speedRef.current); } @@ -65,7 +67,6 @@ const RacingBar = forwardRef( const pause = () => { setPlaying(false); - if (timerRef.current) { clearTimeout(timerRef.current); } @@ -73,10 +74,10 @@ const RacingBar = forwardRef( const next = () => { pause(); - if (monthIndexRef.current < months.length - 1) { + if (monthIndexRef.current < monthsRef.current.length - 1) { const instance = echarts.getInstanceByDom(divEL.current!)!; monthIndexRef.current++; - updateMonth(instance, months[monthIndexRef.current], false); + updateMonth(instance, monthsRef.current[monthIndexRef.current], false); } }; @@ -85,21 +86,53 @@ const RacingBar = forwardRef( if (monthIndexRef.current > 0) { const instance = echarts.getInstanceByDom(divEL.current!)!; monthIndexRef.current--; - updateMonth(instance, months[monthIndexRef.current], false); + updateMonth(instance, monthsRef.current[monthIndexRef.current], false); } }; const latest = () => { pause(); const instance = echarts.getInstanceByDom(divEL.current!)!; - monthIndexRef.current = months.length - 1; - updateMonth(instance, months[monthIndexRef.current], false); + monthIndexRef.current = monthsRef.current.length - 1; + updateMonth(instance, monthsRef.current[monthIndexRef.current], false); }; const earliest = () => { const instance = echarts.getInstanceByDom(divEL.current!)!; monthIndexRef.current = 0; - updateMonth(instance, months[monthIndexRef.current], false); + updateMonth(instance, monthsRef.current[monthIndexRef.current], false); + }; + + const getOpenRankByType = (data: CommunityOpenRankDetails, type: string): CommunityOpenRankDetails => { + if (type === 'a') { + return data; + } + const filteredData: CommunityOpenRankDetails = {}; + + for (const [date, nodes] of Object.entries(data)) { + let typedData = nodes.filter(([_, c]) => c === type); + if (typedData.length != 0) { + filteredData[date] = typedData; + } + } + return filteredData; + }; + + const updateType = (type: string) => { + openRankRef.current = getOpenRankByType(data, type); + monthsRef.current = Object.keys(openRankRef.current); + monthIndexRef.current = monthsRef.current.length - 1; + + getOption( + openRankRef.current, + monthsRef.current[monthIndexRef.current], + speedRef.current, + maxBarsRef.current, + false + ).then((newOption) => { + const instance = echarts.getInstanceByDom(divEL.current!)!; + instance.setOption(newOption); + }); }; // expose startRecording and stopRecording to parent component @@ -110,12 +143,13 @@ const RacingBar = forwardRef( previous, latest, earliest, + updateType, })); useEffect(() => { (async () => { const instance = echarts.init(divEL.current!); - updateMonth(instance, months[monthIndexRef.current], false); + updateMonth(instance, monthsRef.current[monthIndexRef.current], false); })(); return () => { @@ -131,7 +165,7 @@ const RacingBar = forwardRef( return (
-
+
); } diff --git a/src/pages/ContentScripts/features/community-openrank-racing-bar/data.ts b/src/pages/ContentScripts/features/community-openrank-racing-bar/data.ts index e7d4a335..2195d8ca 100644 --- a/src/pages/ContentScripts/features/community-openrank-racing-bar/data.ts +++ b/src/pages/ContentScripts/features/community-openrank-racing-bar/data.ts @@ -31,10 +31,10 @@ export function getMonthlyData(data: CommunityOpenRankDetails) { } /** - * Count the number of unique contributors in the data - * @returns [number of long term contributors, contributors' names] + * Count the number of unique items in the data + * @returns [number of long term items, items' names] */ -export const countLongTermContributors = (data: CommunityOpenRankDetails): [number, string[]] => { +export const countLongTermItems = (data: CommunityOpenRankDetails): number => { const map = new Map(); Object.keys(data).forEach((month) => { data[month].forEach((item) => { @@ -52,7 +52,7 @@ export const countLongTermContributors = (data: CommunityOpenRankDetails): [numb count++; } }); - return [count, [...map.keys()]]; + return count; }; export const DEFAULT_FREQUENCY = 2000; diff --git a/src/pages/ContentScripts/features/community-openrank-racing-bar/index.tsx b/src/pages/ContentScripts/features/community-openrank-racing-bar/index.tsx index 5d216583..fc0e8907 100644 --- a/src/pages/ContentScripts/features/community-openrank-racing-bar/index.tsx +++ b/src/pages/ContentScripts/features/community-openrank-racing-bar/index.tsx @@ -6,11 +6,8 @@ import features from '../../../../feature-manager'; import isPerceptor from '../../../../helpers/is-perceptor'; import { getRepoName, isPublicRepoWithMeta } from '../../../../helpers/get-repo-info'; import { getOpenRank } from '../../../../api/community'; -import { getActivityDetails } from '../../../../api/repo'; import View from './view'; -import DataNotFound from '../repo-networks/DataNotFound'; import { CommunityOpenRankDetails } from './data'; -import { JsonObject } from 'type-fest'; const featureId = features.getFeatureID(import.meta.url); let repoName: string; @@ -28,20 +25,9 @@ const getData = async () => { } } }; -const filterNodesByType = (data: CommunityOpenRankDetails, type: string): CommunityOpenRankDetails => { - const filteredData: CommunityOpenRankDetails = {}; - for (const [date, nodes] of Object.entries(data)) { - filteredData[date] = nodes.filter(([_, c]) => c === type); - } - - return filteredData; -}; -const filterByI = filterNodesByType(communityOpenRankDetails, 'i'); -const filterByP = filterNodesByType(communityOpenRankDetails, 'p'); -const filterByU = filterNodesByType(communityOpenRankDetails, 'u'); const renderTo = (container: Container) => { - render(, container); + render(, container); }; const init = async (): Promise => { diff --git a/src/pages/ContentScripts/features/community-openrank-racing-bar/view.tsx b/src/pages/ContentScripts/features/community-openrank-racing-bar/view.tsx index 3c2ff5f9..e6d4f5c1 100644 --- a/src/pages/ContentScripts/features/community-openrank-racing-bar/view.tsx +++ b/src/pages/ContentScripts/features/community-openrank-racing-bar/view.tsx @@ -1,25 +1,33 @@ import optionsStorage, { HypercrxOptions, defaults } from '../../../../options-storage'; -import RacingBar, { MediaControlers } from './RacingBar'; +import RacingBar, { MediaControllers } from './RacingBar'; import { CommunityOpenRankDetails, getMonthlyData } from './data'; import { PlayerButton } from '../repo-activity-racing-bar/PlayerButton'; import { SpeedController } from '../repo-activity-racing-bar/SpeedController'; import React, { useState, useEffect, useRef } from 'react'; import { Space } from 'antd'; +import { SelectPicker } from 'rsuite'; +import 'rsuite/SelectPicker/styles/index.css'; import { PlayCircleFilled, StepBackwardFilled, StepForwardFilled, PauseCircleFilled } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; import '../../../../helpers/i18n'; + interface Props { - currentRepo: string; communityOpenRankDetails: CommunityOpenRankDetails; } -const View = ({ currentRepo, communityOpenRankDetails }: Props): JSX.Element => { +const View = ({ communityOpenRankDetails }: Props): JSX.Element => { const [options, setOptions] = useState(defaults); const [speed, setSpeed] = useState(1); const [playing, setPlaying] = useState(false); - const mediaControlersRef = useRef(null); + const mediaControllersRef = useRef(null); const { t, i18n } = useTranslation(); + const type = [ + ['All', 'a'], + ['Issue', 'i'], + ['Pull Request', 'p'], + ['User', 'u'], + ].map((item) => ({ label: item[0], value: item[1] })); useEffect(() => { (async function () { setOptions(await optionsStorage.getAll()); @@ -27,12 +35,24 @@ const View = ({ currentRepo, communityOpenRankDetails }: Props): JSX.Element => })(); }, [options.locale]); + const onSelect = (newType: string) => { + mediaControllersRef.current?.updateType(newType); + }; + return (
{t('component_communityOpenRankRacingBar_title')}
+ {/* speed control */} } - onClick={mediaControlersRef.current?.previous} - onLongPress={mediaControlersRef.current?.earliest} + onClick={mediaControllersRef.current?.previous} + onLongPress={mediaControllersRef.current?.earliest} /> {/* play | pause */} : } onClick={() => { if (playing) { - mediaControlersRef.current?.pause(); + mediaControllersRef.current?.pause(); } else { - mediaControlersRef.current?.play(); + mediaControllersRef.current?.play(); } }} /> @@ -66,8 +86,8 @@ const View = ({ currentRepo, communityOpenRankDetails }: Props): JSX.Element => } - onClick={mediaControlersRef.current?.next} - onLongPress={mediaControlersRef.current?.latest} + onClick={mediaControllersRef.current?.next} + onLongPress={mediaControllersRef.current?.latest} /> @@ -77,7 +97,7 @@ const View = ({ currentRepo, communityOpenRankDetails }: Props): JSX.Element =>