From ec3bd59854442647b25497d0058682e49eda1cea Mon Sep 17 00:00:00 2001 From: Harry Date: Thu, 24 Aug 2023 00:18:07 -0400 Subject: [PATCH 01/10] feat: charts design view --- package.json | 1 + .../features/charts-design/index.tsx | 26 + .../features/charts-design/view.tsx | 134 ++++ src/pages/ContentScripts/index.ts | 1 + src/pages/Popup/Popup.tsx | 35 +- yarn.lock | 642 +++++++++++++++++- 6 files changed, 828 insertions(+), 11 deletions(-) create mode 100644 src/pages/ContentScripts/features/charts-design/index.tsx create mode 100644 src/pages/ContentScripts/features/charts-design/view.tsx diff --git a/package.json b/package.json index eb046b57..cfe5f1b7 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@hot-loader/react-dom": "^17.0.2", "@types/chrome": "^0.0.203", "@uifabric/icons": "^7.6.2", + "antd": "^5.8.4", "buffer": "^6.0.3", "delay": "^5.0.0", "dom-loaded": "^3.0.0", diff --git a/src/pages/ContentScripts/features/charts-design/index.tsx b/src/pages/ContentScripts/features/charts-design/index.tsx new file mode 100644 index 00000000..244fc9e7 --- /dev/null +++ b/src/pages/ContentScripts/features/charts-design/index.tsx @@ -0,0 +1,26 @@ +import React, { useState } from 'react'; +import { render, Container } from 'react-dom'; +import $ from 'jquery'; +import View from './view'; + +import features from '../../../../feature-manager'; +import { isPublicRepo } from '../../../../helpers/get-repo-info'; + +const featureId = features.getFeatureID(import.meta.url); + +const renderTo = (container: Container) => { + render(, container); +}; + +const init = async (): Promise => { + const container = document.createElement('div'); + container.id = featureId; + renderTo(container); + $('body').append(container); +}; + +features.add(featureId, { + asLongAs: [isPublicRepo], + awaitDomReady: false, + init, +}); diff --git a/src/pages/ContentScripts/features/charts-design/view.tsx b/src/pages/ContentScripts/features/charts-design/view.tsx new file mode 100644 index 00000000..c8ffc8f5 --- /dev/null +++ b/src/pages/ContentScripts/features/charts-design/view.tsx @@ -0,0 +1,134 @@ +import React, { useState, useEffect } from 'react'; + +import getGithubTheme from '../../../../helpers/get-github-theme'; +import optionsStorage, { + HypercrxOptions, + defaults, +} from '../../../../options-storage'; +import { Modal, Row, Col } from 'antd'; +import Bars from '../../../../components/Bars'; + +const githubTheme = getGithubTheme(); + +const LIGHT_THEME = { + FG_COLOR: '#24292f', + BG_COLOR: '#ffffff', + PALLET: ['#5470c6', '#91cc75'], +}; + +const DARK_THEME = { + FG_COLOR: '#c9d1d9', + BG_COLOR: '#0d1118', + PALLET: ['#58a6ff', '#3fb950'], +}; + +interface Props {} +const data1: any = [ + ['2022-01', 5], + ['2022-02', 10], + ['2022-03', 15], + ['2022-05', 25], + ['2022-06', 30], + ['2022-08', 40], + ['2022-09', 45], +]; +const data2: any = [ + ['2022-01', 12], + ['2022-02', 18], + ['2022-03', 27], + ['2022-04', 8], + ['2022-05', 36], + ['2022-06', 42], + ['2022-07', 20], + ['2022-08', 50], + ['2022-09', 15], +]; + +const mockData = { + bar: { + legend1: 'legend1', + legend2: 'legend2', + yName1: 'yName1', + yName2: 'yName2', + data1: data1, + data2: data2, + }, +}; + +const View = ({}: Props): JSX.Element | null => { + const [options, setOptions] = useState(defaults); + const [isModalOpen, setIsModalOpen] = useState(false); + + const showModal = () => { + setIsModalOpen(true); + }; + + const handleOk = () => { + setIsModalOpen(false); + }; + + useEffect(() => { + (async function () { + setOptions(await optionsStorage.getAll()); + })(); + }, []); + + // receive message from popup + chrome.runtime.onMessage.addListener(function ( + request, + sender, + sendResponse + ) { + if (request.greeting === 'demo') { + showModal(); + focus(); // change the focus to the browser content + } + }); + + return ( +
+ + + +

Bar Chart Light Theme

+
+ +
+ + +

Bar Chart Dark Theme

+
+ +
+ +
+
+
+ ); +}; + +export default View; diff --git a/src/pages/ContentScripts/index.ts b/src/pages/ContentScripts/index.ts index bab274a3..20fe4af2 100644 --- a/src/pages/ContentScripts/index.ts +++ b/src/pages/ContentScripts/index.ts @@ -13,3 +13,4 @@ import './features/repo-networks'; import './features/developer-networks'; import './features/oss-gpt'; import './features/repo-activity-racing-bar'; +import './features/charts-design'; diff --git a/src/pages/Popup/Popup.tsx b/src/pages/Popup/Popup.tsx index 8f39b0d6..814556c9 100644 --- a/src/pages/Popup/Popup.tsx +++ b/src/pages/Popup/Popup.tsx @@ -1,15 +1,34 @@ -import React from 'react'; +import { number } from 'echarts'; +import React, { useState } from 'react'; +import { Space, Button } from 'antd'; export default function Popup() { + // refer: https://developer.chrome.com/docs/extensions/mv3/messaging/#simple + const openFeatureInContentScript = async () => { + const [tab] = await chrome.tabs.query({ + active: true, + lastFocusedWindow: true, + }); + const response = await chrome.tabs.sendMessage(tab.id!, { + greeting: 'demo', + }); + }; return (
- + + + +
); } diff --git a/yarn.lock b/yarn.lock index 32710910..fa45c069 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,6 +10,54 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" +"@ant-design/colors@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-7.0.0.tgz#eb7eecead124c3533aea05d61254f0a17f2b61b3" + integrity sha512-iVm/9PfGCbC0dSMBrz7oiEXZaaGH7ceU40OJEfKmyuzR9R5CRimJYPlRiFtMQGQcbNMea/ePcoIebi4ASGYXtg== + dependencies: + "@ctrl/tinycolor" "^3.4.0" + +"@ant-design/cssinjs@^1.16.0": + version "1.16.2" + resolved "https://registry.yarnpkg.com/@ant-design/cssinjs/-/cssinjs-1.16.2.tgz#4bb4156d721f76043d9a1300038be9f862b1c23a" + integrity sha512-W+LT6Xm5sEYZn7ocMAIP9LvX99woxGg1aYu15o608/uUAaJDR7LrxBu/5cnMLa6AQK1829zdoKmRnRFOxAgzEg== + dependencies: + "@babel/runtime" "^7.11.1" + "@emotion/hash" "^0.8.0" + "@emotion/unitless" "^0.7.5" + classnames "^2.3.1" + csstype "^3.0.10" + rc-util "^5.35.0" + stylis "^4.0.13" + +"@ant-design/icons-svg@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@ant-design/icons-svg/-/icons-svg-4.3.0.tgz#cd8d3624bba50975e848591cea12cb6be132cd82" + integrity sha512-WOgvdH/1Wl8Z7VXigRbCa5djO14zxrNTzvrAQzhWiBQtEKT0uTc8K1ltjKZ8U1gPn/wXhMA8/jE39SJl0WNxSg== + +"@ant-design/icons@^5.2.2": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-5.2.5.tgz#852474359e271a36e54a4ac115065fae7396277e" + integrity sha512-9Jc59v5fl5dzmxqLWtRev3dJwU7Ya9ZheoI6XmZjZiQ7PRtk77rC+Rbt7GJzAPPg43RQ4YO53RE1u8n+Et97vQ== + dependencies: + "@ant-design/colors" "^7.0.0" + "@ant-design/icons-svg" "^4.3.0" + "@babel/runtime" "^7.11.2" + classnames "^2.2.6" + lodash.camelcase "^4.3.0" + rc-util "^5.31.1" + +"@ant-design/react-slick@~1.0.0": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@ant-design/react-slick/-/react-slick-1.0.2.tgz#241bb412aeacf7ff5d50c61fa5db66773fde6b56" + integrity sha512-Wj8onxL/T8KQLFFiCA4t8eIRGpRR+UPgOdac2sYzonv+i0n3kXHmvHLLiOYL655DQx2Umii9Y9nNgL7ssu5haQ== + dependencies: + "@babel/runtime" "^7.10.4" + classnames "^2.2.5" + json2mq "^0.2.0" + resize-observer-polyfill "^1.5.1" + throttle-debounce "^5.0.0" + "@babel/code-frame@^7.21.4": version "7.21.4" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" @@ -983,6 +1031,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.22.5": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.10.tgz#ae3e9631fd947cb7e3610d3e9d8fef5f76696682" + integrity sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.21.9": version "7.21.9" resolved "https://registry.npmjs.org/@babel/template/-/template-7.21.9.tgz#bf8dad2859130ae46088a99c1f265394877446fb" @@ -1017,11 +1072,26 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" +"@ctrl/tinycolor@^3.4.0", "@ctrl/tinycolor@^3.6.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz#53fa5fe9c34faee89469e48f91d51a3766108bc8" + integrity sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ== + "@discoveryjs/json-ext@^0.5.0": version "0.5.7" resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== +"@emotion/hash@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" + integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== + +"@emotion/unitless@^0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" + integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== + "@fluentui/date-time-utilities@^7.9.1": version "7.9.1" resolved "https://registry.npmjs.org/@fluentui/date-time-utilities/-/date-time-utilities-7.9.1.tgz#bb486dc0a0fff33ef5803adabbf95e2cbf4be7be" @@ -1350,6 +1420,73 @@ dependencies: got "13.0.0" +"@rc-component/color-picker@~1.4.0": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@rc-component/color-picker/-/color-picker-1.4.1.tgz#dcab0b660e9c4ed63a7582db68ed4a77c862cb93" + integrity sha512-vh5EWqnsayZa/JwUznqDaPJz39jznx/YDbyBuVJntv735tKXKwEUZZb2jYEldOg+NKWZwtALjGMrNeGBmqFoEw== + dependencies: + "@babel/runtime" "^7.10.1" + "@ctrl/tinycolor" "^3.6.0" + classnames "^2.2.6" + rc-util "^5.30.0" + +"@rc-component/context@^1.3.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@rc-component/context/-/context-1.4.0.tgz#dc6fb021d6773546af8f016ae4ce9aea088395e8" + integrity sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w== + dependencies: + "@babel/runtime" "^7.10.1" + rc-util "^5.27.0" + +"@rc-component/mini-decimal@^1.0.1": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz#7b7a362b14a0a54cb5bc6fd2b82731f29f11d9b0" + integrity sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ== + dependencies: + "@babel/runtime" "^7.18.0" + +"@rc-component/mutate-observer@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz#ee53cc88b78aade3cd0653609215a44779386fd8" + integrity sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw== + dependencies: + "@babel/runtime" "^7.18.0" + classnames "^2.3.2" + rc-util "^5.24.4" + +"@rc-component/portal@^1.0.0-8", "@rc-component/portal@^1.0.0-9", "@rc-component/portal@^1.0.2", "@rc-component/portal@^1.1.0", "@rc-component/portal@^1.1.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@rc-component/portal/-/portal-1.1.2.tgz#55db1e51d784e034442e9700536faaa6ab63fc71" + integrity sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg== + dependencies: + "@babel/runtime" "^7.18.0" + classnames "^2.3.2" + rc-util "^5.24.4" + +"@rc-component/tour@~1.8.1": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@rc-component/tour/-/tour-1.8.1.tgz#a820714b66cb17f317ebd21ac1b45733d2b99183" + integrity sha512-CsrQnfKgNArxx2j1RNHVLZgVA+rLrEj06lIsl4KSynMqADsqz8eKvVkr0F3p9PA10948M6WEEZt5a/FGAbGR2A== + dependencies: + "@babel/runtime" "^7.18.0" + "@rc-component/portal" "^1.0.0-9" + "@rc-component/trigger" "^1.3.6" + classnames "^2.3.2" + rc-util "^5.24.4" + +"@rc-component/trigger@^1.0.4", "@rc-component/trigger@^1.15.0", "@rc-component/trigger@^1.3.6", "@rc-component/trigger@^1.5.0", "@rc-component/trigger@^1.6.2", "@rc-component/trigger@^1.7.0": + version "1.15.5" + resolved "https://registry.yarnpkg.com/@rc-component/trigger/-/trigger-1.15.5.tgz#88ae123b4e2edeadb612ba470b6ac502a076895f" + integrity sha512-HFjeco/gRGAHN3sBl5ZO44o0W6Y3i8sqCQEYcFT1RJJUb91p/uSIWejPDMzHd3DKAdTbRCM3T45jxs7Kwm17kA== + dependencies: + "@babel/runtime" "^7.18.3" + "@rc-component/portal" "^1.1.0" + classnames "^2.3.2" + rc-align "^4.0.0" + rc-motion "^2.0.0" + rc-resize-observer "^1.3.1" + rc-util "^5.33.0" + "@sindresorhus/is@^4.0.0": version "4.6.0" resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" @@ -2026,6 +2163,60 @@ ansi-styles@^5.0.0: resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +antd@^5.8.4: + version "5.8.4" + resolved "https://registry.yarnpkg.com/antd/-/antd-5.8.4.tgz#9225524e8325ebd4bf7a70512fce04a5088c28d4" + integrity sha512-DbQUmRWf9GAllZsc9NxL9gnrup75F7iZ0OlFY+mXh31JdSYQLLP07CAOK7z/sdQLQdYnAHWyuWvkb2mrRKxnYA== + dependencies: + "@ant-design/colors" "^7.0.0" + "@ant-design/cssinjs" "^1.16.0" + "@ant-design/icons" "^5.2.2" + "@ant-design/react-slick" "~1.0.0" + "@babel/runtime" "^7.18.3" + "@ctrl/tinycolor" "^3.6.0" + "@rc-component/color-picker" "~1.4.0" + "@rc-component/mutate-observer" "^1.0.0" + "@rc-component/tour" "~1.8.1" + "@rc-component/trigger" "^1.15.0" + classnames "^2.2.6" + copy-to-clipboard "^3.2.0" + dayjs "^1.11.1" + qrcode.react "^3.1.0" + rc-cascader "~3.14.0" + rc-checkbox "~3.1.0" + rc-collapse "~3.7.0" + rc-dialog "~9.1.0" + rc-drawer "~6.2.0" + rc-dropdown "~4.1.0" + rc-field-form "~1.36.0" + rc-image "~7.1.0" + rc-input "~1.1.0" + rc-input-number "~8.0.2" + rc-mentions "~2.5.0" + rc-menu "~9.10.0" + rc-motion "^2.7.3" + rc-notification "~5.0.4" + rc-pagination "~3.6.0" + rc-picker "~3.13.0" + rc-progress "~3.4.1" + rc-rate "~2.12.0" + rc-resize-observer "^1.2.0" + rc-segmented "~2.2.0" + rc-select "~14.7.1" + rc-slider "~10.1.0" + rc-steps "~6.0.1" + rc-switch "~4.1.0" + rc-table "~7.32.1" + rc-tabs "~12.9.0" + rc-textarea "~1.3.3" + rc-tooltip "~6.0.0" + rc-tree "~5.7.6" + rc-tree-select "~5.11.0" + rc-upload "~4.3.0" + rc-util "^5.32.0" + scroll-into-view-if-needed "^3.0.3" + throttle-debounce "^5.0.0" + anymatch@~3.1.2: version "3.1.3" resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -2085,6 +2276,11 @@ array-flatten@^2.1.2: resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== +array-tree-filter@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz#873ac00fec83749f255ac8dd083814b4f6329190" + integrity sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw== + array-union@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" @@ -2114,6 +2310,11 @@ asn1@^0.2.4: dependencies: safer-buffer "~2.1.0" +async-validator@^4.1.0: + version "4.2.5" + resolved "https://registry.yarnpkg.com/async-validator/-/async-validator-4.2.5.tgz#c96ea3332a521699d0afaaceed510a54656c6339" + integrity sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg== + async@^2.6.3: version "2.6.4" resolved "https://registry.npmjs.org/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" @@ -2392,7 +2593,7 @@ chrome-webstore-upload@^1.0.0: dependencies: got "^11.8.2" -classnames@*, classnames@^2.2.6: +classnames@*, classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2: version "2.3.2" resolved "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== @@ -2506,6 +2707,11 @@ compression@^1.7.4: safe-buffer "5.1.2" vary "~1.1.2" +compute-scroll-into-view@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-3.0.3.tgz#c418900a5c56e2b04b885b54995df164535962b1" + integrity sha512-nadqwNxghAGTamwIqQSG433W6OADZx2vCo3UXHNrzTRHK/htu+7+L0zhjEoaeaQVNAi3YgqWDv8+tzf0hRfR+A== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2543,6 +2749,13 @@ cookie@0.5.0: resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== +copy-to-clipboard@^3.2.0: + version "3.3.3" + resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0" + integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA== + dependencies: + toggle-selection "^1.0.6" + copy-webpack-plugin@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-7.0.0.tgz#3506f867ca6e861ee2769d4deaf8fa0d2563ada9" @@ -2645,7 +2858,7 @@ cssesc@^3.0.0: resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -csstype@^3.0.2: +csstype@^3.0.10, csstype@^3.0.2: version "3.1.2" resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== @@ -2657,6 +2870,11 @@ date-fns@^2.11.1: dependencies: "@babel/runtime" "^7.21.0" +dayjs@^1.11.1: + version "1.11.9" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a" + integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA== + debug@2.6.9: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -2765,6 +2983,11 @@ dns-packet@^5.2.2: dependencies: "@leichtgewicht/ip-codec" "^2.0.1" +dom-align@^1.7.0: + version "1.12.4" + resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.12.4.tgz#3503992eb2a7cfcb2ed3b2a6d21e0b9c00d54511" + integrity sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw== + dom-converter@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" @@ -3739,6 +3962,13 @@ json-schema-traverse@^1.0.0: resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== +json2mq@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a" + integrity sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA== + dependencies: + string-convert "^0.2.0" + json5@^2.1.2, json5@^2.2.2: version "2.2.3" resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" @@ -3818,6 +4048,11 @@ lodash-es@^4.17.21: resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -4517,6 +4752,11 @@ punycode@^2.1.0: resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== +qrcode.react@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-3.1.0.tgz#5c91ddc0340f768316fbdb8fff2765134c2aecd8" + integrity sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q== + qs@6.11.0: version "6.11.0" resolved "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" @@ -4561,6 +4801,365 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" +rc-align@^4.0.0: + version "4.0.15" + resolved "https://registry.yarnpkg.com/rc-align/-/rc-align-4.0.15.tgz#2bbd665cf85dfd0b0244c5a752b07565e9098577" + integrity sha512-wqJtVH60pka/nOX7/IspElA8gjPNQKIx/ZqJ6heATCkXpe1Zg4cPVrMD2vC96wjsFFL8WsmhPbx9tdMo1qqlIA== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "2.x" + dom-align "^1.7.0" + rc-util "^5.26.0" + resize-observer-polyfill "^1.5.1" + +rc-cascader@~3.14.0: + version "3.14.1" + resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-3.14.1.tgz#495f00b8d047a54fa64df3102f4d6e4a664feaf2" + integrity sha512-fCsgjLIQqYZMhFj9UT+x2ZW4uobx7OP5yivcn6Xto5fuxHaldphsryzCeUVmreQOHEo0RP+032Ip9RDzrKVKJA== + dependencies: + "@babel/runtime" "^7.12.5" + array-tree-filter "^2.1.0" + classnames "^2.3.1" + rc-select "~14.7.0" + rc-tree "~5.7.0" + rc-util "^5.35.0" + +rc-checkbox@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/rc-checkbox/-/rc-checkbox-3.1.0.tgz#6be0d9d8de2cc96fb5e37f9036a1c3e360d0a42d" + integrity sha512-PAwpJFnBa3Ei+5pyqMMXdcKYKNBMS+TvSDiLdDnARnMJHC8ESxwPfm4Ao1gJiKtWLdmGfigascnCpwrHFgoOBQ== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.3.2" + rc-util "^5.25.2" + +rc-collapse@~3.7.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/rc-collapse/-/rc-collapse-3.7.1.tgz#bda1f7f80adccf3433c1c15d4d9f9ca09910c727" + integrity sha512-N/7ejyiTf3XElNJBBpxqnZBUuMsQWEOPjB2QkfNvZ/Ca54eAvJXuOD1EGbCWCk2m7v/MSxku7mRpdeaLOCd4Gg== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "2.x" + rc-motion "^2.3.4" + rc-util "^5.27.0" + +rc-dialog@~9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/rc-dialog/-/rc-dialog-9.1.0.tgz#6bf6fcc0453503b7643e54a5a445e835e3850649" + integrity sha512-5ry+JABAWEbaKyYsmITtrJbZbJys8CtMyzV8Xn4LYuXMeUx5XVHNyJRoqLFE4AzBuXXzOWeaC49cg+XkxK6kHA== + dependencies: + "@babel/runtime" "^7.10.1" + "@rc-component/portal" "^1.0.0-8" + classnames "^2.2.6" + rc-motion "^2.3.0" + rc-util "^5.21.0" + +rc-drawer@~6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/rc-drawer/-/rc-drawer-6.2.0.tgz#fddf4825b0fa9d60e317b996f70278d594d1f668" + integrity sha512-spPkZ3WvP0U0vy5dyzSwlUJ/+vLFtjP/cTwSwejhQRoDBaexSZHsBhELoCZcEggI7LQ7typmtG30lAue2HEhvA== + dependencies: + "@babel/runtime" "^7.10.1" + "@rc-component/portal" "^1.1.1" + classnames "^2.2.6" + rc-motion "^2.6.1" + rc-util "^5.21.2" + +rc-dropdown@~4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/rc-dropdown/-/rc-dropdown-4.1.0.tgz#418a68939631520de80d0865d02b440eeeb4168e" + integrity sha512-VZjMunpBdlVzYpEdJSaV7WM7O0jf8uyDjirxXLZRNZ+tAC+NzD3PXPEtliFwGzVwBBdCmGuSqiS9DWcOLxQ9tw== + dependencies: + "@babel/runtime" "^7.18.3" + "@rc-component/trigger" "^1.7.0" + classnames "^2.2.6" + rc-util "^5.17.0" + +rc-field-form@~1.36.0: + version "1.36.2" + resolved "https://registry.yarnpkg.com/rc-field-form/-/rc-field-form-1.36.2.tgz#0a4e75ab9849e3c2517b8b07c1f97ecd3e52db55" + integrity sha512-tCF/JjUsnxW80Gk4E4ZH74ONsaQMxVTRtui6XhQB8DJc4FHWLLa5pP8zwhxtPKC5NaO0QZ0Cv79JggDubn6n2g== + dependencies: + "@babel/runtime" "^7.18.0" + async-validator "^4.1.0" + rc-util "^5.32.2" + +rc-image@~7.1.0: + version "7.1.3" + resolved "https://registry.yarnpkg.com/rc-image/-/rc-image-7.1.3.tgz#0072547c7c0a70e6badfb4bee320806c5bf7427b" + integrity sha512-foMl1rcit1F0+vgxE5kf0c8TygQcHhILsOohQUL+JMUbzOo3OBFRcehJudYbqbCTArzCecS8nA1irUU9vvgQbg== + dependencies: + "@babel/runtime" "^7.11.2" + "@rc-component/portal" "^1.0.2" + classnames "^2.2.6" + rc-dialog "~9.1.0" + rc-motion "^2.6.2" + rc-util "^5.34.1" + +rc-input-number@~8.0.2: + version "8.0.4" + resolved "https://registry.yarnpkg.com/rc-input-number/-/rc-input-number-8.0.4.tgz#d33cfe4126e10f4771fe11a40797222c76d6598f" + integrity sha512-TP+G5b7mZtbwXJ/YEZXF/OgbEZ6iqD4+RSuxZJ8VGKGXDcdt0FKIvpFoNQr/knspdFC4OxA0OfsWfFWfN4XSyA== + dependencies: + "@babel/runtime" "^7.10.1" + "@rc-component/mini-decimal" "^1.0.1" + classnames "^2.2.5" + rc-input "~1.1.0" + rc-util "^5.28.0" + +rc-input@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/rc-input/-/rc-input-1.1.1.tgz#af33b49272220f6d42852d21b22e84c2dc1a87e6" + integrity sha512-NTR1Z4em681L8/ewb2KR80RykSmN8I2mzqzJDCoUmTrV1BB9Hk5d7ha4TnfgdEPPL148N+603sW2LExSXk1IbA== + dependencies: + "@babel/runtime" "^7.11.1" + classnames "^2.2.1" + rc-util "^5.18.1" + +rc-mentions@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/rc-mentions/-/rc-mentions-2.5.0.tgz#8b936e497e0deb922f40df46e42efc3f596ec207" + integrity sha512-rERXsbUTNVrb5T/iDC0ki/SRGWJnOVraDy6O25Us3FSpuUZ3uq2TPZB4fRk0Hss5kyiEPzz2sprhkI4b+F4jUw== + dependencies: + "@babel/runtime" "^7.22.5" + "@rc-component/trigger" "^1.5.0" + classnames "^2.2.6" + rc-input "~1.1.0" + rc-menu "~9.10.0" + rc-textarea "~1.3.0" + rc-util "^5.22.5" + +rc-menu@~9.10.0: + version "9.10.0" + resolved "https://registry.yarnpkg.com/rc-menu/-/rc-menu-9.10.0.tgz#5e0982e26786d67c8ebdba50406b197884c749a7" + integrity sha512-g27kpXaAoJh/fkPZF65/d4V+w4DhDeqomBdPcGnkFAcJnEM4o21TnVccrBUoDedLKzC7wJRw1Q7VTqEsfEufmw== + dependencies: + "@babel/runtime" "^7.10.1" + "@rc-component/trigger" "^1.6.2" + classnames "2.x" + rc-motion "^2.4.3" + rc-overflow "^1.3.1" + rc-util "^5.27.0" + +rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.3.0, rc-motion@^2.3.4, rc-motion@^2.4.3, rc-motion@^2.4.4, rc-motion@^2.6.0, rc-motion@^2.6.1, rc-motion@^2.6.2, rc-motion@^2.7.3: + version "2.7.3" + resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.7.3.tgz#126155bb3e687174fb3b92fddade2835c963b04d" + integrity sha512-2xUvo8yGHdOHeQbdI8BtBsCIrWKchEmFEIskf0nmHtJsou+meLd/JE+vnvSX2JxcBrJtXY2LuBpxAOxrbY/wMQ== + dependencies: + "@babel/runtime" "^7.11.1" + classnames "^2.2.1" + rc-util "^5.21.0" + +rc-notification@~5.0.4: + version "5.0.5" + resolved "https://registry.yarnpkg.com/rc-notification/-/rc-notification-5.0.5.tgz#33a86864b7491749742cfaef0df0117a9b967926" + integrity sha512-uEz2jggourwv/rR0obe7RHEa63UchqX4k+e+Qt2c3LaY7U9Tc+L6ANhzgCKYSA/afm0ebjmNZHoB5Cv47xEOcA== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "2.x" + rc-motion "^2.6.0" + rc-util "^5.20.1" + +rc-overflow@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/rc-overflow/-/rc-overflow-1.3.1.tgz#03224cf90c66aa570eb0deeb4eff6cc96401e979" + integrity sha512-RY0nVBlfP9CkxrpgaLlGzkSoh9JhjJLu6Icqs9E7CW6Ewh9s0peF9OHIex4OhfoPsR92LR0fN6BlCY9Z4VoUtA== + dependencies: + "@babel/runtime" "^7.11.1" + classnames "^2.2.1" + rc-resize-observer "^1.0.0" + rc-util "^5.19.2" + +rc-pagination@~3.6.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-3.6.1.tgz#2db6678a57cd2f4f29d6c0416e282543af52d0df" + integrity sha512-R/sUnKKXx1Nm4kZfUKS3YKa7yEPF1ZkVB/AynQaHt+nMER7h9wPTfliDJFdYo+RM/nk2JD4Yc5QpUq8fIQHeug== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.1" + rc-util "^5.32.2" + +rc-picker@~3.13.0: + version "3.13.0" + resolved "https://registry.yarnpkg.com/rc-picker/-/rc-picker-3.13.0.tgz#b5bec6dbaa7d8a1e4ca9f4e93863a8a75607a62d" + integrity sha512-hJ+1lGkemnvsW+t+PjH9OAehHlj7wdD0G75T1HZj0IeZTqBE/5mmuf8E8MHYATNBqW409lAfk8GwjYm1WVMopg== + dependencies: + "@babel/runtime" "^7.10.1" + "@rc-component/trigger" "^1.5.0" + classnames "^2.2.1" + rc-util "^5.30.0" + +rc-progress@~3.4.1: + version "3.4.2" + resolved "https://registry.yarnpkg.com/rc-progress/-/rc-progress-3.4.2.tgz#f8df9ee95e790490171ab6b31bf07303cdc79980" + integrity sha512-iAGhwWU+tsayP+Jkl9T4+6rHeQTG9kDz8JAHZk4XtQOcYN5fj9H34NXNEdRdZx94VUDHMqCb1yOIvi8eJRh67w== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.6" + rc-util "^5.16.1" + +rc-rate@~2.12.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/rc-rate/-/rc-rate-2.12.0.tgz#0182deffed3b009cdcc61660da8746c39ed91ed5" + integrity sha512-g092v5iZCdVzbjdn28FzvWebK2IutoVoiTeqoLTj9WM7SjA/gOJIw5/JFZMRyJYYVe1jLAU2UhAfstIpCNRozg== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.5" + rc-util "^5.0.1" + +rc-resize-observer@^1.0.0, rc-resize-observer@^1.1.0, rc-resize-observer@^1.2.0, rc-resize-observer@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/rc-resize-observer/-/rc-resize-observer-1.3.1.tgz#b61b9f27048001243617b81f95e53d7d7d7a6a3d" + integrity sha512-iFUdt3NNhflbY3mwySv5CA1TC06zdJ+pfo0oc27xpf4PIOvfZwZGtD9Kz41wGYqC4SLio93RVAirSSpYlV/uYg== + dependencies: + "@babel/runtime" "^7.20.7" + classnames "^2.2.1" + rc-util "^5.27.0" + resize-observer-polyfill "^1.5.1" + +rc-segmented@~2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/rc-segmented/-/rc-segmented-2.2.2.tgz#a34f12ce6c0975fc3042ae7656bcd18e1744798e" + integrity sha512-Mq52M96QdHMsNdE/042ibT5vkcGcD5jxKp7HgPC2SRofpia99P5fkfHy1pEaajLMF/kj0+2Lkq1UZRvqzo9mSA== + dependencies: + "@babel/runtime" "^7.11.1" + classnames "^2.2.1" + rc-motion "^2.4.4" + rc-util "^5.17.0" + +rc-select@~14.7.0, rc-select@~14.7.1: + version "14.7.4" + resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-14.7.4.tgz#742d85861e83604237784f60e2ba9dabcde8eac9" + integrity sha512-qRUpvMVXFy6rdHe+qzHXAqyQAfhErC/oY8dcRtoRjoz0lz2Nx3J+lLL5AnEbjnwlS+/kQTJUZ/65WyCwWwcLwQ== + dependencies: + "@babel/runtime" "^7.10.1" + "@rc-component/trigger" "^1.5.0" + classnames "2.x" + rc-motion "^2.0.1" + rc-overflow "^1.3.1" + rc-util "^5.16.1" + rc-virtual-list "^3.5.2" + +rc-slider@~10.1.0: + version "10.1.1" + resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-10.1.1.tgz#5e82036e60b61021aba3ea0e353744dd7c74e104" + integrity sha512-gn8oXazZISEhnmRinI89Z/JD/joAaM35jp+gDtIVSTD/JJMCCBqThqLk1SVJmvtfeiEF/kKaFY0+qt4SDHFUDw== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.5" + rc-util "^5.27.0" + +rc-steps@~6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/rc-steps/-/rc-steps-6.0.1.tgz#c2136cd0087733f6d509209a84a5c80dc29a274d" + integrity sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g== + dependencies: + "@babel/runtime" "^7.16.7" + classnames "^2.2.3" + rc-util "^5.16.1" + +rc-switch@~4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/rc-switch/-/rc-switch-4.1.0.tgz#f37d81b4e0c5afd1274fd85367b17306bf25e7d7" + integrity sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg== + dependencies: + "@babel/runtime" "^7.21.0" + classnames "^2.2.1" + rc-util "^5.30.0" + +rc-table@~7.32.1: + version "7.32.3" + resolved "https://registry.yarnpkg.com/rc-table/-/rc-table-7.32.3.tgz#9773563dc206ff12b6f023b7223b7056908d6241" + integrity sha512-MqjrI/ibuGg7NEyFsux0dM5GK+3er1gTiZofAkifr2bHf/Sa1nUqXXFmSrYXSOjwpx0xyBnJ3GrHFCIqC/eOzw== + dependencies: + "@babel/runtime" "^7.10.1" + "@rc-component/context" "^1.3.0" + classnames "^2.2.5" + rc-resize-observer "^1.1.0" + rc-util "^5.27.1" + +rc-tabs@~12.9.0: + version "12.9.0" + resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-12.9.0.tgz#6d9af43d8ad2c47be00c75bee92417a4842d29d2" + integrity sha512-2HnVowgMVrq0DfQtyu4mCd9E6pXlWNdM6VaDvOOHMsLYqPmpY+7zBqUC6YrrQ9xYXHciTS0e7TtjOHIvpVCHLQ== + dependencies: + "@babel/runtime" "^7.11.2" + classnames "2.x" + rc-dropdown "~4.1.0" + rc-menu "~9.10.0" + rc-motion "^2.6.2" + rc-resize-observer "^1.0.0" + rc-util "^5.16.0" + +rc-textarea@~1.3.0, rc-textarea@~1.3.3: + version "1.3.4" + resolved "https://registry.yarnpkg.com/rc-textarea/-/rc-textarea-1.3.4.tgz#e77baf2202ac8f7e34a50ec9e15dd1dcb1501455" + integrity sha512-wn0YjTpvcVolcfXa0HtzL+jgV2QcwtfB29RwNAKj8hMgZOju1V24M3TfEDjABeQEAQbUGbjMbISREOX/YSVKhg== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.1" + rc-input "~1.1.0" + rc-resize-observer "^1.0.0" + rc-util "^5.27.0" + +rc-tooltip@~6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-6.0.1.tgz#6a5e33bd6c3f6afe8851ea90e7af43e5c26b3cc6" + integrity sha512-MdvPlsD1fDSxKp9+HjXrc/CxLmA/s11QYIh1R7aExxfodKP7CZA++DG1AjrW80F8IUdHYcR43HAm0Y2BYPelHA== + dependencies: + "@babel/runtime" "^7.11.2" + "@rc-component/trigger" "^1.0.4" + classnames "^2.3.1" + +rc-tree-select@~5.11.0: + version "5.11.1" + resolved "https://registry.yarnpkg.com/rc-tree-select/-/rc-tree-select-5.11.1.tgz#7a667288fae06ec06d362ed85d0902068d801407" + integrity sha512-EDG1rYFu1iD2Y8fg0yEmm0LV3XqWOy+SpgOMvO5396NgAZ67t0zVTNK6FQkIxzdXf5ri742BkB/B8+Ah6+0Kxw== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "2.x" + rc-select "~14.7.0" + rc-tree "~5.7.0" + rc-util "^5.16.1" + +rc-tree@~5.7.0, rc-tree@~5.7.6: + version "5.7.9" + resolved "https://registry.yarnpkg.com/rc-tree/-/rc-tree-5.7.9.tgz#e0df730ffbba1df95901fd3b108267288056e162" + integrity sha512-1hKkToz/EVjJlMVwmZnpXeLXt/1iQMsaAq9m+GNkUbK746gkc7QpJXSN/TzjhTI5Hi+LOSlrMaXLMT0bHPqILQ== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "2.x" + rc-motion "^2.0.1" + rc-util "^5.16.1" + rc-virtual-list "^3.5.1" + +rc-upload@~4.3.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/rc-upload/-/rc-upload-4.3.4.tgz#83ff7d3867631c37adbfd72ea3d1fd7e97ca84af" + integrity sha512-uVbtHFGNjHG/RyAfm9fluXB6pvArAGyAx8z7XzXXyorEgVIWj6mOlriuDm0XowDHYz4ycNK0nE0oP3cbFnzxiQ== + dependencies: + "@babel/runtime" "^7.18.3" + classnames "^2.2.5" + rc-util "^5.2.0" + +rc-util@^5.0.1, rc-util@^5.16.0, rc-util@^5.16.1, rc-util@^5.17.0, rc-util@^5.18.1, rc-util@^5.19.2, rc-util@^5.2.0, rc-util@^5.20.1, rc-util@^5.21.0, rc-util@^5.21.2, rc-util@^5.22.5, rc-util@^5.24.4, rc-util@^5.25.2, rc-util@^5.26.0, rc-util@^5.27.0, rc-util@^5.27.1, rc-util@^5.28.0, rc-util@^5.30.0, rc-util@^5.31.1, rc-util@^5.32.0, rc-util@^5.32.2, rc-util@^5.33.0, rc-util@^5.34.1, rc-util@^5.35.0, rc-util@^5.36.0: + version "5.36.0" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.36.0.tgz#be21995071e148f81141edb6f767062db5170224" + integrity sha512-a4uUvT+UNHvYL+awzbN8H8zAjfduwY4KAp2wQy40wOz3NyBdo3Xhx/EAAPyDkHLoGm535jIACaMhIqExGiAjHw== + dependencies: + "@babel/runtime" "^7.18.3" + react-is "^16.12.0" + +rc-virtual-list@^3.5.1, rc-virtual-list@^3.5.2: + version "3.10.5" + resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.10.5.tgz#a203ca60bf3334e16193f641db1e99a48ae76574" + integrity sha512-Vc89TL3JHfRlLVQXVj5Hmv0dIflgwmHDcbjt9lrZjOG3wNUDkTF5zci8kFDU/CzdmmqgKu+CUktEpT10VUKYSQ== + dependencies: + "@babel/runtime" "^7.20.0" + classnames "^2.2.6" + rc-resize-observer "^1.0.0" + rc-util "^5.36.0" + react-chat-widget@^3.1.4: version "3.1.4" resolved "https://registry.npmjs.org/react-chat-widget/-/react-chat-widget-3.1.4.tgz#e1017e5ec20f667b3f9c6835052c58adcb9c42f0" @@ -4599,7 +5198,7 @@ react-hot-loader@^4.13.0: shallowequal "^1.1.0" source-map "^0.7.3" -react-is@^16.13.1, react-is@^16.7.0: +react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -4712,6 +5311,11 @@ regenerator-runtime@^0.13.11: resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + regenerator-transform@^0.15.1: version "0.15.1" resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" @@ -4764,6 +5368,11 @@ requires-port@^1.0.0: resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== +resize-observer-polyfill@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" + integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== + resolve-alpn@^1.0.0, resolve-alpn@^1.2.0: version "1.2.1" resolved "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" @@ -4910,6 +5519,13 @@ schema-utils@^4.0.0: ajv-formats "^2.1.1" ajv-keywords "^5.1.0" +scroll-into-view-if-needed@^3.0.3: + version "3.0.10" + resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.10.tgz#38fbfe770d490baff0fb2ba34ae3539f6ec44e13" + integrity sha512-t44QCeDKAPf1mtQH3fYpWz8IM/DyvHLjs8wUvvwMYxk5moOqCzrMSxK6HQVD0QVmVjXFavoFIPRVrMuJPKAvtg== + dependencies: + compute-scroll-into-view "^3.0.2" + select-hose@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" @@ -5132,6 +5748,11 @@ statuses@2.0.1: resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== +string-convert@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97" + integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A== + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -5165,6 +5786,11 @@ strip-indent@^4.0.0: dependencies: min-indent "^1.0.1" +stylis@^4.0.13: + version "4.3.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.0.tgz#abe305a669fc3d8777e10eefcfc73ad861c5588c" + integrity sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ== + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -5228,6 +5854,11 @@ terser@^5.10.0, terser@^5.16.8: commander "^2.20.0" source-map-support "~0.5.20" +throttle-debounce@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-5.0.0.tgz#a17a4039e82a2ed38a5e7268e4132d6960d41933" + integrity sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg== + thunky@^1.0.2: version "1.1.0" resolved "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" @@ -5245,6 +5876,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toggle-selection@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" + integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== + toidentifier@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" From 86a33577ffc659115b82f9a3b42abf2260062be1 Mon Sep 17 00:00:00 2001 From: Harry Date: Thu, 19 Oct 2023 00:01:34 -0400 Subject: [PATCH 02/10] fix: Popup width is too narrow --- src/pages/Popup/Popup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Popup/Popup.tsx b/src/pages/Popup/Popup.tsx index 814556c9..fa045d9c 100644 --- a/src/pages/Popup/Popup.tsx +++ b/src/pages/Popup/Popup.tsx @@ -15,7 +15,7 @@ export default function Popup() { }; return (
- + + +

+ Add to collections +

+ + +
+ Select collections you want the current repository to be added to. +
+
+ {/* Checklist */} +
+ {allCollections.map((collection) => { + const checked = checkedCollectionIds.includes(collection.id); + return CheckListItem(collection, handleCheckChange, checked); + })} +
+ {/* 3 buttons */} +
+
+ + + +
+
+
+ + ); +}; diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionButton/CollectionList.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionButton/CollectionList.tsx new file mode 100644 index 00000000..73ece968 --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/CollectionButton/CollectionList.tsx @@ -0,0 +1,127 @@ +import { useRepoCollectionContext } from '../context'; +import { Collection } from '../context/store'; + +import React from 'react'; + +const ListItem = ( + collection: Collection, + onClick: (collectionId: Collection['id']) => void +) => { + const handleClick = () => { + onClick(collection.id); + }; + + return ( +
+ + {collection.name} + +
+ ); +}; + +/** + * The modal that shows the collections that the repo belongs to + */ +export const CollectionList = () => { + const { + currentRepositoryCollections, + hideCollectionList, + setHideAddToCollections, + setHideCollectionList, + setSelectedCollection, + setShowManageModal, + } = useRepoCollectionContext(); + + const handleCollectionClick = (collectionId: Collection['id']) => { + setSelectedCollection(collectionId); + setShowManageModal(true); + }; + + const goToAddToCollections = () => { + setHideAddToCollections(false); + setHideCollectionList(true); + }; + + return ( + + ); +}; diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionButton/index.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionButton/index.tsx new file mode 100644 index 00000000..74b65cb2 --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/CollectionButton/index.tsx @@ -0,0 +1,41 @@ +import { CollectionList } from './CollectionList'; +import { AddToCollections } from './AddToCollections'; +import { useRepoCollectionContext } from '../context'; + +import React from 'react'; +import { FundProjectionScreenOutlined } from '@ant-design/icons'; + +/** + * The "Collections" button, which is in the left of the "Edit Pins" button + */ +export const CollectionButton = () => { + const { currentRepositoryCollections } = useRepoCollectionContext(); + + return ( +
+
+ + + + {' Collections '} + + + {currentRepositoryCollections.length} + + + + + +
+
+ ); +}; diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionModal/CollectionEditor.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionModal/CollectionEditor.tsx new file mode 100644 index 00000000..734207cf --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/CollectionModal/CollectionEditor.tsx @@ -0,0 +1,298 @@ +import { useRepoCollectionContext } from '../context'; + +import React, { useEffect, useState } from 'react'; +import { + Modal, + Col, + Row, + Button, + Form, + Input, + Table, + Divider, + Radio, +} from 'antd'; +import type { ColumnsType } from 'antd/es/table'; + +interface Values { + name: string; + quickImport: string; +} + +interface DataType { + key: React.Key; + name: string; + description: string; +} + +interface RepositoryInfo { + name: string; + description: string; +} + +interface CollectionEditorProps { + open: boolean; + onCreate: (values: Values, newRepoData: string[] | undefined) => void; + onCancel: () => void; + isEdit: boolean | undefined; + collectionName: string; + collectionData: string[]; +} + +interface DataSourceType { + key: string; + name: string; + description: string; +} + +const accessTokens = ['token']; + +let currentTokenIndex = 0; + +async function getUserOrOrgRepos( + username: string, + isOrg: boolean +): Promise { + try { + const currentAccessToken = accessTokens[currentTokenIndex]; + + const apiUrl = isOrg + ? `https://api.github.com/orgs/${username}/repos` + : `https://api.github.com/users/${username}/repos`; + + const response = await fetch(apiUrl, { + headers: { + Authorization: `Bearer ${currentAccessToken}`, + }, + }); + + if (!response.ok) { + if (response.status === 401) { + currentTokenIndex = (currentTokenIndex + 1) % accessTokens.length; // switch to next token + return getUserOrOrgRepos(username, isOrg); + } else { + throw new Error( + `GitHub API request failed with status: ${response.status}` + ); + } + } + + const reposData = await response.json(); + + const repositories: RepositoryInfo[] = reposData.map((repo: any) => ({ + name: repo.name, + description: repo.description || '', + })); + + return repositories; + } catch (error) { + console.error('Error fetching repositories:', error); + throw error; + } +} + +// TODO 需要找到一个合适的方法解决Token的问题... + +const columns: ColumnsType = [ + { + title: 'name', + dataIndex: 'name', + }, + { + title: 'description', + dataIndex: 'description', + }, +]; + +const CollectionEditor: React.FC = ({ + open, + onCreate, + onCancel, + isEdit, + collectionName, + collectionData, +}) => { + const { allCollections } = useRepoCollectionContext(); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [form] = Form.useForm(); + const [dataSource, setDataSource] = useState(); + const [newRepoData, setNewRepoData] = useState(collectionData); + const [isOrg, setIsOrg] = useState(false); + + async function fetchRepositoryDescription(repositoryName: string) { + const apiUrl = `https://api.github.com/repos/${repositoryName}`; + + const response = await fetch(apiUrl); + if (!response.ok) { + throw new Error( + `GitHub API request failed for ${repositoryName} with status: ${response.status}` + ); + } + const repoData = await response.json(); + return { + key: collectionData.indexOf(repositoryName).toString(), + name: repositoryName, + description: repoData.description || '', + }; + } + + useEffect(() => { + Promise.all( + collectionData.map((repositoryName) => + fetchRepositoryDescription(repositoryName) + ) + ) + .then((repositoryDescriptions) => { + setDataSource(repositoryDescriptions); + }) + .catch((error) => { + console.error('Error fetching repository descriptions:', error); + }); + }, []); + + const initialValues = { + collectionName: isEdit ? collectionName : '', + }; + const modalTitle = isEdit ? 'Collection Editor' : 'Create a new collection'; + + const onSelectChange = ( + newSelectedRowKeys: React.Key[], + selectedRows: DataType[] + ) => { + setNewRepoData(selectedRows.map((item) => item.name)); + setSelectedRowKeys(newSelectedRowKeys); + }; + + const defaultSelectedRowKeys: React.Key[] = Array.from( + { length: collectionData.length }, + (_, index) => index.toString() + ); + const rowSelection = { + defaultSelectedRowKeys, + onChange: onSelectChange, + }; + + const handleInquireClick = () => { + const inputValue = form.getFieldValue('Quick import'); + async function fetchRepositories() { + try { + const result = await getUserOrOrgRepos(inputValue, isOrg); + let nextKey: number; + if (dataSource) { + nextKey = dataSource.length + 1; + } else { + nextKey = 1; + } + const addKeyValue = [ + ...result.map((repo) => ({ + key: (nextKey++).toString(), + name: repo.name, + description: repo.description, + })), + ]; + if (dataSource) { + setDataSource([...dataSource, ...addKeyValue]); + } else { + setDataSource(addKeyValue); + } + } catch (error) { + // 处理错误 + console.error('Error:', error); + } + } + + fetchRepositories(); + }; + + function handleImportClick() { + console.log('newRepoData', newRepoData); + } + + return ( + { + form + .validateFields() + .then((values) => { + form.resetFields(); + onCreate(values, newRepoData); + }) + .catch((info) => { + console.log('Validate Failed:', info); + }); + }} + > +
+ { + const existingNames = allCollections; + if (existingNames.some((item) => item.name === value)) { + return Promise.reject('Collection name already exists.'); + } + return Promise.resolve(); + }, + }, + ]} + > + + + + + + +
+ { + const selectedValue = e.target.value; + setIsOrg(selectedValue === 'Organization'); + }} + > + User + Organization + + + +
+
+ + + + + + + + ); +}; + +export default CollectionEditor; diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.tsx new file mode 100644 index 00000000..b5281f80 --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.tsx @@ -0,0 +1,255 @@ +import { useRepoCollectionContext } from '../context'; +import CollectionEditor from './CollectionEditor'; + +import React, { useState, useEffect } from 'react'; +import { Modal, Tabs, List, Col, Row, Button } from 'antd'; + +type TargetKey = React.MouseEvent | React.KeyboardEvent | string; + +type CollectionTabType = { + label: string; + children: string; + key: string; +}; + +export const CollectionManageModal = () => { + const { + showManageModal, + setShowManageModal, + selectedCollection, + setSelectedCollection, + updaters, + allCollections, + allRelations, + } = useRepoCollectionContext(); + + const [activeKey, setActiveKey] = useState(); + const [items, setItems] = useState([]); + const [listData, setListData] = useState( + allRelations + .filter((relation) => relation.collectionId === allCollections[0].id) + .map((relation) => relation.repositoryId) + ); + const [isClick, setIsClick] = useState(false); + const [isEdit, setIsEdit] = useState(); + + const editTab = ( +
+ + +
+ ); + + useEffect(() => { + const initialItems = allCollections.map((collection, index) => ({ + label: collection.name, + children: `Content of ${collection.name}`, + key: collection.id, + })); + const initialListData = allRelations + .filter((relation) => + relation.collectionId === selectedCollection + ? selectedCollection + : allCollections[0].name + ) + .map((relation) => relation.repositoryId); + setActiveKey(selectedCollection); + setItems(initialItems); + setListData(initialListData); + }, [showManageModal]); + + useEffect(() => {}, []); + + const onCreate = async (values: any, newRepoData: string[] | undefined) => { + if (isEdit) { + const updatedItems = items.map((item) => { + if (item.key === activeKey?.toString()) { + return { ...item, label: values.collectionName }; + } + return item; + }); + setItems(updatedItems); + } else { + const newPanes = [...items]; + newPanes.push({ + label: values.collectionName, + children: `Content of ${values.collectionName}`, + key: values.collectionName, + }); + setItems(newPanes); + setActiveKey(values.collectionName); + } + + try { + /* + * remove collection and its relations + */ + if (selectedCollection) { + await updaters.removeCollection(selectedCollection); + const relationsToRemove = allRelations.filter( + (relation) => relation.collectionId === selectedCollection + ); + await updaters.removeRelations(relationsToRemove); + } + + /* + * add newCollection and its relations + */ + + await updaters.addCollection({ + id: values.collectionName, + name: values.collectionName, + }); + if (newRepoData) { + const relationsToAdd = newRepoData.map((repo) => ({ + collectionId: values.collectionName, + repositoryId: repo, + })); + await updaters.addRelations(relationsToAdd); + } + } catch (error) { + console.error('Error:', error); + } + console.log('Received values of form: ', values); + + setListData(newRepoData); + setSelectedCollection(values.collectionName); + setIsClick(false); + setIsEdit(undefined); + }; + + const onChange = (newActiveKey: string) => { + setActiveKey(newActiveKey); + setSelectedCollection(newActiveKey); + }; + + const remove = (targetKey: TargetKey) => { + Modal.confirm({ + title: 'Confirm Deletion', + content: 'Are you sure you want to delete this collection?', + okText: 'Confirm', + async onOk() { + // 用户点击确认按钮时执行的操作 + let newActiveKey = activeKey; + let lastIndex = -1; + items.forEach((item, i) => { + if (item.key === targetKey) { + lastIndex = i - 1; + } + }); + const newPanes = items.filter((item) => item.key !== targetKey); + if (newPanes.length && newActiveKey === targetKey) { + if (lastIndex >= 0) { + newActiveKey = newPanes[lastIndex].key; + } else { + newActiveKey = newPanes[0].key; + } + } + setItems(newPanes); + setActiveKey(newActiveKey); + await updaters.removeCollection(targetKey.toString()); + await updaters.removeRelations( + allRelations.filter( + (relation) => relation.collectionId === targetKey.toString() + ) + ); + setSelectedCollection(newActiveKey); + }, + onCancel() {}, + }); + }; + + const onEdit = ( + targetKey: React.MouseEvent | React.KeyboardEvent | string, + action: 'add' | 'remove' + ) => { + if (action === 'remove') remove(targetKey); + }; + + return ( +
+ { + setShowManageModal(false); + setSelectedCollection(undefined); + }} + footer={null} + width={'100%'} + style={{ + top: '0px', + bottom: '0px', + height: '100vh', + maxWidth: 'unset', + }} + bodyStyle={{ height: 'calc(100vh - 40px)' }} // 40px is the sum of top and bottom padding + > + +
+
+ + {selectedCollection + ? selectedCollection + : 'Select tab first'} +
+ } + bordered + dataSource={allRelations + .filter( + (relation) => relation.collectionId === selectedCollection + ) + .map((relation) => relation.repositoryId)} + renderItem={(item) => {item}} + /> + + + + + + + + {isClick && ( + { + setIsClick(false); + setIsEdit(undefined); + }} + isEdit={isEdit} + collectionName={selectedCollection ? selectedCollection : ''} + collectionData={allRelations + .filter((relation) => relation.collectionId === selectedCollection) + .map((relation) => relation.repositoryId)} + /> + )} + + ); +}; diff --git a/src/pages/ContentScripts/features/repo-collection/context/index.ts b/src/pages/ContentScripts/features/repo-collection/context/index.ts new file mode 100644 index 00000000..6953774d --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/context/index.ts @@ -0,0 +1,48 @@ +import { Repository } from './store'; +import { useStore } from './useStore'; + +import { useState } from 'react'; +import constate from 'constate'; + +const useRepoCollection = ({ + currentRepositoryId, +}: { + currentRepositoryId: Repository['id']; +}) => { + const { allCollections, allRelations, updaters } = useStore(); + // get all related collections for the current repository + const currentRepositoryRelations = allRelations.filter( + (r) => r.repositoryId === currentRepositoryId + ); + const currentRepositoryCollections = allCollections.filter((c) => + currentRepositoryRelations.some((r) => r.collectionId === c.id) + ); + + const [hideCollectionList, setHideCollectionList] = useState(false); + const [hideAddToCollections, setHideAddToCollections] = useState(true); + + const [showManageModal, setShowManageModal] = useState(false); + const [selectedCollection, setSelectedCollection] = useState(); + + return { + currentRepositoryId, + currentRepositoryCollections, + allCollections, + allRelations, + updaters, + + hideCollectionList, + setHideCollectionList, + hideAddToCollections, + setHideAddToCollections, + + showManageModal, + setShowManageModal, + + selectedCollection, + setSelectedCollection, + }; +}; + +export const [RepoCollectionProvider, useRepoCollectionContext] = + constate(useRepoCollection); diff --git a/src/pages/ContentScripts/features/repo-collection/context/store.ts b/src/pages/ContentScripts/features/repo-collection/context/store.ts new file mode 100644 index 00000000..d7b25530 --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/context/store.ts @@ -0,0 +1,202 @@ +export interface Repository { + id: string; + /** e.g. microsoft/vscode */ + fullName: string; +} + +export interface Collection { + id: string; + name: string; +} + +export interface Relation { + repositoryId: Repository['id']; + collectionId: Collection['id']; +} + +const RELATIONS_STORE_KEY = + 'hypercrx:repo-collection:repository-collection-relations'; +const COLLECTIONS_STORE_KEY = 'hypercrx:repo-collection:collections'; + +// TODO: delete other collections except for X-lab before the PR is merged +const defaultCollections: Collection[] = [ + { + id: 'X-lab', + name: 'X-lab', + }, + { + id: 'Hypertrons', + name: 'Hypertrons', + }, + { + id: 'Mulan', + name: 'Mulan', + }, +]; +const defaultRelations: Relation[] = [ + { + repositoryId: 'X-lab2017/open-digger', + collectionId: 'X-lab', + }, + { + repositoryId: 'hypertrons/hypertrons-crx', + collectionId: 'X-lab', + }, + { + repositoryId: 'hypertrons/hypertrons-crx', + collectionId: 'Hypertrons', + }, +]; + +/** + * Store for repo collection + */ +class RepoCollectionStore { + private static instance: RepoCollectionStore; + // a simple lock mechanism to prevent concurrent updates + private isUpdatingRelations: boolean = false; + private isUpdatingCollection: boolean = false; + + public static getInstance(): RepoCollectionStore { + if (!RepoCollectionStore.instance) { + RepoCollectionStore.instance = new RepoCollectionStore(); + } + return RepoCollectionStore.instance; + } + + public async addCollection(collection: Collection): Promise { + if (this.isUpdatingCollection) { + // Another update is in progress, wait for it to finish + await this.waitForUpdateToFinish(); + } + + this.isUpdatingCollection = true; + + try { + const collections = await this.getAllCollections(); + collections.push(collection); + await chrome.storage.sync.set({ + [COLLECTIONS_STORE_KEY]: collections, + }); + } finally { + this.isUpdatingCollection = false; + } + } + + public async removeCollection(collectionId: Collection['id']): Promise { + if (this.isUpdatingCollection) { + // Another update is in progress, wait for it to finish + await this.waitForUpdateToFinish(); + } + + this.isUpdatingCollection = true; + + try { + const collections = await this.getAllCollections(); + const index = collections.findIndex((c) => c.id === collectionId); + if (index === -1) { + return; + } + // Remove its relations first + const relations = await this.getAllRelations(); + relations.forEach((r) => { + if (r.collectionId === collectionId) { + this.removeRelations([r]); + } + }); + // Then remove the collection + collections.splice(index, 1); + await chrome.storage.sync.set({ + [COLLECTIONS_STORE_KEY]: collections, + }); + } finally { + this.isUpdatingCollection = false; + } + } + + public async getAllCollections(): Promise { + const collections = await chrome.storage.sync.get({ + [COLLECTIONS_STORE_KEY]: defaultCollections, + }); + return collections[COLLECTIONS_STORE_KEY]; + } + + public async addRelations(relations: Relation[]): Promise { + if (this.isUpdatingRelations) { + // Another update is in progress, wait for it to finish + await this.waitForUpdateToFinish(); + } + + this.isUpdatingRelations = true; + + try { + const allRelations = await this.getAllRelations(); + // Remove duplicate relations + relations = relations.filter((r) => { + return ( + allRelations.findIndex( + (rr) => + rr.repositoryId === r.repositoryId && + rr.collectionId === r.collectionId + ) === -1 + ); + }); + allRelations.push(...relations); + await chrome.storage.sync.set({ + [RELATIONS_STORE_KEY]: allRelations, + }); + } finally { + this.isUpdatingRelations = false; + } + } + + public async removeRelations(relations: Relation[]): Promise { + if (this.isUpdatingRelations) { + // Another update is in progress, wait for it to finish + await this.waitForUpdateToFinish(); + } + + this.isUpdatingRelations = true; + + try { + const allRelations = await this.getAllRelations(); + relations.forEach((r) => { + const index = allRelations.findIndex( + (rr) => + rr.repositoryId === r.repositoryId && + rr.collectionId === r.collectionId + ); + if (index !== -1) { + allRelations.splice(index, 1); + } + }); + await chrome.storage.sync.set({ + [RELATIONS_STORE_KEY]: allRelations, + }); + } finally { + this.isUpdatingRelations = false; + } + } + + public async getAllRelations(): Promise { + const relations = await chrome.storage.sync.get({ + [RELATIONS_STORE_KEY]: defaultRelations, + }); + return relations[RELATIONS_STORE_KEY]; + } + + private async waitForUpdateToFinish(): Promise { + return new Promise((resolve) => { + const check = () => { + if (!this.isUpdatingRelations) { + resolve(); + } else { + setTimeout(check, 10); // Check again after a short delay + } + }; + check(); + }); + } +} + +export const repoCollectionStore = RepoCollectionStore.getInstance(); diff --git a/src/pages/ContentScripts/features/repo-collection/context/useStore.ts b/src/pages/ContentScripts/features/repo-collection/context/useStore.ts new file mode 100644 index 00000000..ac278acc --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/context/useStore.ts @@ -0,0 +1,56 @@ +import { repoCollectionStore, Collection, Relation } from './store'; + +import { useState, useEffect } from 'react'; + +export const useStore = () => { + const [allCollections, setAllCollections] = useState([]); + const [allRelations, setAllRelations] = useState([]); + + const fetchAllCollections = async () => { + const collections = await repoCollectionStore.getAllCollections(); + setAllCollections(collections); + }; + + const fetchAllRelations = async () => { + const relations = await repoCollectionStore.getAllRelations(); + setAllRelations(relations); + }; + + const addRelations = async (relations: Relation[]) => { + await repoCollectionStore.addRelations(relations); + fetchAllRelations(); + }; + + const removeRelations = async (relations: Relation[]) => { + await repoCollectionStore.removeRelations(relations); + fetchAllRelations(); + }; + + const addCollection = async (collection: Collection) => { + await repoCollectionStore.addCollection(collection); + fetchAllCollections(); + }; + + const removeCollection = async (collectionId: Collection['id']) => { + await repoCollectionStore.removeCollection(collectionId); + fetchAllCollections(); + }; + + const updaters = { + addRelations, + removeRelations, + addCollection, + removeCollection, + }; + + useEffect(() => { + fetchAllCollections(); + fetchAllRelations(); + }, []); + + return { + allCollections, + allRelations, + updaters, + }; +}; diff --git a/src/pages/ContentScripts/features/repo-collection/index.tsx b/src/pages/ContentScripts/features/repo-collection/index.tsx new file mode 100644 index 00000000..b072699a --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/index.tsx @@ -0,0 +1,39 @@ +import features from '../../../../feature-manager'; +import { isPublicRepo, getRepoName } from '../../../../helpers/get-repo-info'; +import View from './view'; + +import React from 'react'; +import { render, Container } from 'react-dom'; +import $ from 'jquery'; +import elementReady from 'element-ready'; + +const featureId = features.getFeatureID(import.meta.url); +let repoName: string; + +const renderTo = (container: Container) => { + render(, container); +}; + +const init = async (): Promise => { + repoName = getRepoName(); + + const container = document.createElement('li'); + container.id = featureId; + renderTo(container); + await elementReady('#repository-details-container'); + $('#repository-details-container>ul').prepend(container); +}; + +const restore = async () => { + if (repoName !== getRepoName()) { + repoName = getRepoName(); + } + renderTo($(`#${featureId}`)[0]); +}; + +features.add(featureId, { + include: [isPublicRepo], + awaitDomReady: true, + init, + restore, +}); diff --git a/src/pages/ContentScripts/features/repo-collection/view.tsx b/src/pages/ContentScripts/features/repo-collection/view.tsx new file mode 100644 index 00000000..6fe28a00 --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/view.tsx @@ -0,0 +1,20 @@ +import { CollectionButton } from './CollectionButton'; +import { CollectionManageModal } from './CollectionModal'; +import { RepoCollectionProvider } from './context'; + +import React from 'react'; + +interface Props { + repoName: string; +} + +const View = ({ repoName }: Props) => { + return ( + + + + + ); +}; + +export default View; diff --git a/src/pages/ContentScripts/index.ts b/src/pages/ContentScripts/index.ts index 20fe4af2..9288402a 100644 --- a/src/pages/ContentScripts/index.ts +++ b/src/pages/ContentScripts/index.ts @@ -13,4 +13,4 @@ import './features/repo-networks'; import './features/developer-networks'; import './features/oss-gpt'; import './features/repo-activity-racing-bar'; -import './features/charts-design'; +import './features/repo-collection'; diff --git a/src/pages/Popup/Popup.tsx b/src/pages/Popup/Popup.tsx index fa045d9c..d30998ff 100644 --- a/src/pages/Popup/Popup.tsx +++ b/src/pages/Popup/Popup.tsx @@ -14,8 +14,8 @@ export default function Popup() { }); }; return ( -
- +
+
-

Bar Chart Light Theme

-
- -
- - -

Bar Chart Dark Theme

-
- -
- - - - - ); -}; - -export default View; diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionContent/ChartCard.css b/src/pages/ContentScripts/features/repo-collection/CollectionContent/ChartCard.css new file mode 100644 index 00000000..3ed0d540 --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/CollectionContent/ChartCard.css @@ -0,0 +1,6 @@ +/* ChartCard.css */ + +.custom-card { + background-color: #fafafa; + /* 其他样式属性 */ +} diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionContent/ChartCard.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionContent/ChartCard.tsx new file mode 100644 index 00000000..13856c10 --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/CollectionContent/ChartCard.tsx @@ -0,0 +1,23 @@ +import React, { ReactNode } from 'react'; +import { Card } from 'antd'; +import './ChartCard.css'; // 导入自定义样式 + +interface ChartCardProps { + title: ReactNode; + children: React.ReactNode; +} + +function ChartCard({ title, children }: ChartCardProps) { + return ( + + {children} + + ); +} + +export default ChartCard; diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionContent/CollectionDashboard.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionContent/CollectionDashboard.tsx new file mode 100644 index 00000000..1ea7b3ce --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/CollectionContent/CollectionDashboard.tsx @@ -0,0 +1,170 @@ +import LineChart from '../charts/LineChart'; +import BarChart from '../charts/BarChart'; +import SankeyChart from '../charts/SankeyChart'; +import PieChart from '../charts/PieChart'; +import StackedBarChart from '../charts/StackedBarChart'; +import CodeStackedBarChart from '../charts/CodeStackedBarChart'; +import BoxplotChart from '../charts/BoxplotChart'; +import ChartCard from './ChartCard'; +import NumericPanel from '../charts/NumericPanel'; + +import React from 'react'; +import { Row, Col } from 'antd'; + +interface CollectionDashboardProps { + repoNames: string[]; + currentRepo?: string; +} + +const CollectionDashboard: React.FC = ({ + repoNames, + currentRepo, +}) => { + return ( +
+ +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + ); +}; + +export default CollectionDashboard; diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.tsx new file mode 100644 index 00000000..62e74824 --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.tsx @@ -0,0 +1,56 @@ +// Index.tsx +import React, { useState, useEffect } from 'react'; +import { Layout, Menu, theme } from 'antd'; + +import CollectionDashboard from './CollectionDashboard'; + +const { Content, Sider } = Layout; +interface Index { + repoNames: string[]; + + currentRepo?: string; +} + +const LIGHT_THEME = { + BG_COLOR: '#ffffff', +}; + +const CollectionContent: React.FC = ({ repoNames, currentRepo }) => { + const menuItems = repoNames.map((repo, index) => ({ + key: index, + label: repo, + })); + const { + token: { colorBgContainer }, + } = theme.useToken(); + + // 添加一个状态来跟踪选中的仓库名 + const [selectedRepo, setSelectedRepo] = useState( + undefined + ); + useEffect(() => { + setSelectedRepo(currentRepo); + }, [currentRepo]); + const handleMenuClick = (key: string) => { + setSelectedRepo(key); + }; + + return ( + + + handleMenuClick(key)} //点击切换选中的repo + /> + + + + + + ); +}; + +export default CollectionContent; diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionModal/CollectionEditor.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionModal/CollectionEditor.tsx index 734207cf..e89235dd 100644 --- a/src/pages/ContentScripts/features/repo-collection/CollectionModal/CollectionEditor.tsx +++ b/src/pages/ContentScripts/features/repo-collection/CollectionModal/CollectionEditor.tsx @@ -32,7 +32,7 @@ interface RepositoryInfo { interface CollectionEditorProps { open: boolean; - onCreate: (values: Values, newRepoData: string[] | undefined) => void; + onCreate: (values: Values, newRepoData: string[]) => void; onCancel: () => void; isEdit: boolean | undefined; collectionName: string; diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.tsx index b5281f80..72e22132 100644 --- a/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.tsx +++ b/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.tsx @@ -1,5 +1,6 @@ import { useRepoCollectionContext } from '../context'; import CollectionEditor from './CollectionEditor'; +import CollectionContent from '../CollectionContent'; import React, { useState, useEffect } from 'react'; import { Modal, Tabs, List, Col, Row, Button } from 'antd'; @@ -8,7 +9,7 @@ type TargetKey = React.MouseEvent | React.KeyboardEvent | string; type CollectionTabType = { label: string; - children: string; + children: React.ReactNode; key: string; }; @@ -25,7 +26,7 @@ export const CollectionManageModal = () => { const [activeKey, setActiveKey] = useState(); const [items, setItems] = useState([]); - const [listData, setListData] = useState( + const [listData, setListData] = useState( allRelations .filter((relation) => relation.collectionId === allCollections[0].id) .map((relation) => relation.repositoryId) @@ -58,11 +59,16 @@ export const CollectionManageModal = () => { ); useEffect(() => { - const initialItems = allCollections.map((collection, index) => ({ - label: collection.name, - children: `Content of ${collection.name}`, - key: collection.id, - })); + const initialItems = allCollections.map((collection) => { + const repoList = allRelations + .filter((relation) => relation.collectionId === collection.name) + .map((relation) => relation.repositoryId); + return { + label: collection.name, + children: , + key: collection.id, + }; + }); const initialListData = allRelations .filter((relation) => relation.collectionId === selectedCollection @@ -77,7 +83,7 @@ export const CollectionManageModal = () => { useEffect(() => {}, []); - const onCreate = async (values: any, newRepoData: string[] | undefined) => { + const onCreate = async (values: any, newRepoData: string[]) => { if (isEdit) { const updatedItems = items.map((item) => { if (item.key === activeKey?.toString()) { @@ -90,7 +96,7 @@ export const CollectionManageModal = () => { const newPanes = [...items]; newPanes.push({ label: values.collectionName, - children: `Content of ${values.collectionName}`, + children: , key: values.collectionName, }); setItems(newPanes); @@ -199,41 +205,17 @@ export const CollectionManageModal = () => { height: '100vh', maxWidth: 'unset', }} - bodyStyle={{ height: 'calc(100vh - 40px)' }} // 40px is the sum of top and bottom padding + bodyStyle={{ height: 'calc(100vh - 40px)', overflow: 'auto' }} // 40px is the sum of top and bottom padding > - - -
- - {selectedCollection - ? selectedCollection - : 'Select tab first'} -
- } - bordered - dataSource={allRelations - .filter( - (relation) => relation.collectionId === selectedCollection - ) - .map((relation) => relation.repositoryId)} - renderItem={(item) => {item}} - /> - - - - - - + {isClick && ( { + const { theme, height, repoNames, currentRepo } = props; + const divEL = useRef(null); + const TH = theme == 'light' ? LIGHT_THEME : DARK_THEME; + const [data, setData] = useState<{ [repo: string]: RawRepoData }>({}); + + const option: echarts.EChartsOption = { + tooltip: { + trigger: 'axis', + }, + // legend: { + // type: 'scroll', + // }, + grid: { + left: '5%', + right: '4%', + bottom: '3%', + containLabel: true, + }, + xAxis: { + type: 'time', + splitLine: { + show: false, + }, + axisLabel: { + color: TH.FG_COLOR, + formatter: { + year: '{yearStyle|{yy}}', + month: '{MMM}', + }, + rich: { + yearStyle: { + fontWeight: 'bold', + }, + }, + }, + }, + yAxis: { + type: 'value', + }, + dataZoom: [ + { + type: 'inside', + start: 0, + end: 100, + minValueSpan: 3600 * 24 * 1000 * 180, + }, + ], + series: BarChartSeries(data), // / Utilize the transformed series data + }; + console.log('bar', BarChartSeries(data)); + + useEffect(() => { + const fetchData = async () => { + for (const repo of repoNames) { + try { + //getStars() to fetch repository data + const starsData = await getStars(repo); + // Update Data/ + setData((prevData) => ({ ...prevData, [repo]: starsData })); + } catch (error) { + console.error(`Error fetching stars data for ${repo}:`, error); + // If the retrieval fails, set the data to an empty object + setData((prevData) => ({ ...prevData, [repo]: {} })); + } + } + }; + fetchData(); + }, []); + + useEffect(() => { + let chartDOM = divEL.current; + const TH = 'light' ? LIGHT_THEME : DARK_THEME; + + const instance = echarts.init(chartDOM as any); + instance.setOption(option); + instance.dispatchAction({ + type: 'highlight', + // seriesIndex: Number(currentRepo), + // dataIndex: Number(currentRepo), + name: repoNames[Number(currentRepo)], + seriesName: repoNames[Number(currentRepo)], + }); + return () => { + instance.dispose(); + }; + }, [data, currentRepo]); + + return
; +}; +const BarChartSeries = (data: { + [repo: string]: RawRepoData; +}): echarts.SeriesOption[] => + Object.entries(data).map(([repoName, repoData]) => ({ + name: repoName, + type: 'bar', + symbol: 'none', + data: getLastSixMonth(generateDataByMonth(repoData)), + emphasis: { + focus: 'series', + }, + yAxisIndex: 0, + triggerLineEvent: true, + })); + +const getLastSixMonth = (data: any[]) => + data.length > 6 ? data.slice(-6) : data; + +export default BarChart; diff --git a/src/pages/ContentScripts/features/repo-collection/charts/BoxplotChart.tsx b/src/pages/ContentScripts/features/repo-collection/charts/BoxplotChart.tsx new file mode 100644 index 00000000..65d9d45c --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/charts/BoxplotChart.tsx @@ -0,0 +1,159 @@ +import React, { useEffect, useRef, useState } from 'react'; +import * as echarts from 'echarts'; +import { getIssueResponseTime } from '../../../../../api/repo'; +import getNewestMonth from '../../../../../helpers/get-newest-month'; + +const LIGHT_THEME = { + FG_COLOR: '#24292f', + BG_COLOR: '#ffffff', + PALLET: ['#5470c6', '#91cc75'], +}; + +const DARK_THEME = { + FG_COLOR: '#c9d1d9', + BG_COLOR: '#0d1118', + PALLET: ['#58a6ff', '#3fb950'], +}; + +interface BarChartProps { + theme: 'light' | 'dark'; + height: number; + repoNames: string[]; + + currentRepo?: string; +} + +const BoxplotChart = (props: BarChartProps): JSX.Element => { + const { theme, height, repoNames, currentRepo } = props; + const divEL = useRef(null); + const TH = theme == 'light' ? LIGHT_THEME : DARK_THEME; + const [data, setData] = useState<{}>({}); + + console.log('Boxplot_data,', lastMonthRepoData(data)); + const option: echarts.EChartsOption = { + tooltip: { + trigger: 'item', + axisPointer: { + type: 'shadow', + }, + }, + legend: { + type: 'scroll', + }, + grid: { + left: '5%', + right: '4%', + bottom: '3%', + containLabel: true, + }, + xAxis: { + show: false, + type: 'category', + boundaryGap: true, + nameGap: 30, + splitArea: { + show: false, + }, + // data: Object.keys(data), + splitLine: { + show: false, + }, + // data: lastMonthRepoData(data).map((repo) => repo.name), + }, + yAxis: { + type: 'value', + name: 'value', + splitArea: { + show: true, + }, + }, + // dataZoom: [ + // { + // type: 'inside', + // start: 0, + // end: 100, + // minValueSpan: 3600 * 24 * 1000 * 180, + // }, + // ], + series: lastMonthRepoData(data).map((repoData) => { + return { + type: 'boxplot', + name: repoData.name, + data: [repoData], + }; + }), + // { + // type: 'boxplot', + // data: lastMonthRepoData(data), + // }, + }; + useEffect(() => { + const fetchData = async () => { + for (const repo of repoNames) { + try { + //getStars() to fetch repository data + const starsData = await getIssueResponseTime(repo); + // console.log('starsDatastarsData', starsData); + // Update Data/ + setData((prevData) => ({ ...prevData, [repo]: starsData })); + } catch (error) { + console.error(`Error fetching stars data for ${repo}:`, error); + // If the retrieval fails, set the data to an empty object + setData((prevData) => ({ ...prevData, [repo]: {} })); + } + } + }; + fetchData(); + // console.log('data', data); + }, []); + + useEffect(() => { + let chartDOM = divEL.current; + const TH = 'light' ? LIGHT_THEME : DARK_THEME; + + const instance = echarts.init(chartDOM as any); + // console.log('data', data); + // console.log('lastMonthRepoData', lastMonthRepoData(data)); + instance.setOption(option); + instance.dispatchAction({ + type: 'highlight', + seriesIndex: Number(currentRepo), + dataIndex: Number(currentRepo), + name: repoNames[Number(currentRepo)], + seriesName: repoNames[Number(currentRepo)], + }); + return () => { + instance.dispose(); + }; + }, [data, currentRepo]); + + return
; +}; + +//原始数据 =>各仓库最近一个月的数据[] +function lastMonthRepoData(repo_data: any) { + const resultArray = []; + const lastMonth = getNewestMonth(); + for (const repoName in repo_data) { + if (repo_data.hasOwnProperty(repoName)) { + const lastMonthData = { + name: repoName, + value: + repo_data[repoName][`avg`][lastMonth] !== undefined + ? Array.from( + { length: 5 }, + (_, q) => repo_data[repoName][`quantile_${q}`][lastMonth] + ) + : [null, null, null, null, null], + }; + + resultArray.push(lastMonthData); + // 将转换后的数据存储为对象,并添加到结果数组中 + // console.log('repoName', repoName); + // console.log('lastM', repo_data[repoName][`avg`][lastMonth]); + } + } + return resultArray; +} + +export default BoxplotChart; diff --git a/src/pages/ContentScripts/features/repo-collection/charts/CodeStackedBarChart.tsx b/src/pages/ContentScripts/features/repo-collection/charts/CodeStackedBarChart.tsx new file mode 100644 index 00000000..430a369c --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/charts/CodeStackedBarChart.tsx @@ -0,0 +1,179 @@ +import React, { useEffect, useRef, useState } from 'react'; +import * as echarts from 'echarts'; +import generateDataByMonth from '../../../../../helpers/generate-data-by-month'; +import { + getMergedCodeAddition, + getMergedCodeDeletion, +} from '../../../../../api/repo'; + +interface RawRepoData { + [date: string]: number; +} + +const LIGHT_THEME = { + FG_COLOR: '#24292f', + BG_COLOR: '#ffffff', + PALLET: ['#5470c6', '#91cc75'], +}; + +const DARK_THEME = { + FG_COLOR: '#c9d1d9', + BG_COLOR: '#0d1118', + PALLET: ['#58a6ff', '#3fb950'], +}; + +interface StackedBarChartProps { + theme: 'light' | 'dark'; + height: number; + repoNames: string[]; + + currentRepo?: string; +} + +const CodeStackedBarChart = (props: StackedBarChartProps): JSX.Element => { + const { theme, height, repoNames, currentRepo } = props; + const divEL = useRef(null); + const TH = theme == 'light' ? LIGHT_THEME : DARK_THEME; + const [data, setData] = useState<{ [repo: string]: RawRepoData }>({}); + + const option: echarts.EChartsOption = { + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross', // 设置 axisPointer 的类型为 cross,即十字准星 + }, + // formatter: (params: any) => { + // console.log('params',params); + + // return result; + // }, + }, + legend: { + type: 'scroll', + }, + grid: { + left: '5%', + right: '4%', + bottom: '3%', + containLabel: true, + }, + xAxis: { + type: 'time', + splitLine: { + show: false, + }, + axisLabel: { + color: TH.FG_COLOR, + formatter: { + year: '{yearStyle|{yy}}', + month: '{MMM}', + }, + rich: { + yearStyle: { + fontWeight: 'bold', + }, + }, + }, + }, + yAxis: { + type: 'value', + }, + dataZoom: [ + { + type: 'inside', + start: 0, + end: 100, + minValueSpan: 3600 * 24 * 1000 * 180, + }, + ], + series: MCDeletionSeries(data).concat(MCAdditionSeries(data)), // Series Data: Code Addition + Code CodeDeletion + }; + console.log( + 'BarChartSeries', + MCDeletionSeries(data).concat(MCAdditionSeries(data)) + ); + useEffect(() => { + const fetchData = async () => { + for (const repo of repoNames) { + try { + const MCAdditionData = await getMergedCodeAddition(repo); + const MCDeletionData = await getMergedCodeDeletion(repo); + const MergedCodeData = { + MCAdditionData: MCAdditionData, + MCDeletionData: MCDeletionData, + }; + setData((prevData) => ({ ...prevData, [repo]: MergedCodeData })); + } catch (error) { + console.error(`Error fetching stars data for ${repo}:`, error); + + setData((prevData) => ({ ...prevData, [repo]: {} })); + } + } + }; + fetchData(); + }, []); + console.log('data', data); + useEffect(() => { + let chartDOM = divEL.current; + const TH = 'light' ? LIGHT_THEME : DARK_THEME; + + const instance = echarts.init(chartDOM as any); + instance.setOption(option); + instance.dispatchAction({ + type: 'highlight', + // seriesIndex: Number(currentRepo), + // dataIndex: Number(currentRepo), + name: repoNames[Number(currentRepo)], + seriesName: repoNames[Number(currentRepo)], + }); + return () => { + instance.dispose(); + }; + }, [data, currentRepo]); + + return
; +}; + +//Series:各仓库代码增加行数 +const MCAdditionSeries = (data: { + [repo: string]: RawRepoData; +}): echarts.SeriesOption[] => + Object.entries(data).map(([repoName, repoData]) => ({ + name: repoName, + type: 'line', + areaStyle: {}, + smooth: true, + symbol: 'none', + stack: repoName, + data: generateDataByMonth(repoData.MCAdditionData), + emphasis: { + focus: 'series', + }, + yAxisIndex: 0, + triggerLineEvent: true, + })); + +//Series:各仓库代码删减行数 +const MCDeletionSeries = (data: { + [repo: string]: RawRepoData; +}): echarts.SeriesOption[] => + Object.entries(data).map(([repoName, repoData]) => ({ + name: repoName, + type: 'line', + areaStyle: {}, + symbol: 'none', + smooth: true, + stack: repoName, + data: generateDataByMonth(repoData.MCDeletionData).map((item) => [ + item[0], + -item[1], + ]), + emphasis: { + focus: 'series', + }, + yAxisIndex: 0, + triggerLineEvent: true, + })); + +//const getLastSixMonth = (data: any[]) => (data.length > 6 ? data.slice(-6) : data); +export default CodeStackedBarChart; diff --git a/src/pages/ContentScripts/features/repo-collection/charts/LineChart.tsx b/src/pages/ContentScripts/features/repo-collection/charts/LineChart.tsx new file mode 100644 index 00000000..508da19e --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/charts/LineChart.tsx @@ -0,0 +1,138 @@ +import React, { useEffect, useRef, useState } from 'react'; +import * as echarts from 'echarts'; +import generateDataByMonth from '../../../../../helpers/generate-data-by-month'; +import { getStars } from '../../../../../api/repo'; + +interface RawRepoData { + [date: string]: number; +} + +const LIGHT_THEME = { + FG_COLOR: '#24292f', + BG_COLOR: '#ffffff', + PALLET: ['#5470c6', '#91cc75'], +}; + +const DARK_THEME = { + FG_COLOR: '#c9d1d9', + BG_COLOR: '#0d1118', + PALLET: ['#58a6ff', '#3fb950'], +}; + +interface LineChartProps { + theme: 'light' | 'dark'; + height: number; + repoNames: string[]; + + currentRepo?: string; +} + +const LineChart = (props: LineChartProps): JSX.Element => { + const { theme, height, repoNames, currentRepo } = props; + + const divEL = useRef(null); + const TH = theme == 'light' ? LIGHT_THEME : DARK_THEME; + const [data, setData] = useState<{ [repo: string]: RawRepoData }>({}); + + const option: echarts.EChartsOption = { + tooltip: { + trigger: 'axis', + }, + legend: { + type: 'scroll', + }, + grid: { + left: '5%', + right: '4%', + bottom: '3%', + containLabel: true, + }, + xAxis: { + type: 'time', + splitLine: { + show: false, + }, + axisLabel: { + color: TH.FG_COLOR, + formatter: { + year: '{yearStyle|{yy}}', + month: '{MMM}', + }, + rich: { + yearStyle: { + fontWeight: 'bold', + }, + }, + }, + }, + yAxis: { + type: 'value', + }, + dataZoom: [ + { + type: 'slider', + }, + { + type: 'inside', + // start: 0, + // end: 100, + minValueSpan: 3600 * 24 * 1000 * 180, + }, + ], + series: LineChartSeries(data), + }; + + useEffect(() => { + const fetchData = async () => { + for (const repo of repoNames) { + try { + //getStars() to fetch repository data + const starsData = await getStars(repo); + // Update Data + setData((prevData) => ({ ...prevData, [repo]: starsData })); + } catch (error) { + console.error(`Error fetching stars data for ${repo}:`, error); + // If the retrieval fails, set the data to an empty object + setData((prevData) => ({ ...prevData, [repo]: {} })); + } + } + }; + fetchData(); + }, []); + + useEffect(() => { + let chartDOM = divEL.current; + const TH = 'light' ? LIGHT_THEME : DARK_THEME; + + const instance = echarts.init(chartDOM as any); + instance.setOption(option); + instance.dispatchAction({ + type: 'highlight', + // seriesIndex: Number(currentRepo), + // dataIndex: Number(currentRepo), + name: repoNames[Number(currentRepo)], + seriesName: repoNames[Number(currentRepo)], + }); + return () => { + instance.dispose(); + }; + }, [data, currentRepo]); + + return
; +}; +const LineChartSeries = (data: { + [repo: string]: RawRepoData; +}): echarts.SeriesOption[] => + Object.entries(data).map(([repoName, repoData]) => ({ + name: repoName, + type: 'line', + symbol: 'none', + smooth: true, + data: generateDataByMonth(repoData), + emphasis: { + focus: 'series', + }, + yAxisIndex: 0, + triggerLineEvent: true, + })); +export default LineChart; diff --git a/src/pages/ContentScripts/features/repo-collection/charts/NumericPanel.tsx b/src/pages/ContentScripts/features/repo-collection/charts/NumericPanel.tsx new file mode 100644 index 00000000..e60735bf --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/charts/NumericPanel.tsx @@ -0,0 +1,101 @@ +import React, { useEffect, useRef, useState } from 'react'; +import * as echarts from 'echarts'; +import generateDataByMonth from '../../../../../helpers/generate-data-by-month'; +import { getStars } from '../../../../../api/repo'; +import getNewestMonth from '../../../../../helpers/get-newest-month'; + +interface RawRepoData { + [date: string]: number; +} + +const LIGHT_THEME = { + FG_COLOR: '#24292f', + BG_COLOR: '#ffffff', + PALLET: ['#5470c6', '#91cc75'], +}; + +const DARK_THEME = { + FG_COLOR: '#c9d1d9', + BG_COLOR: '#0d1118', + PALLET: ['#58a6ff', '#3fb950'], +}; + +interface BarChartProps { + theme: 'light' | 'dark'; + height: number; + repoNames: string[]; + currentRepo?: string; +} + +const NumericPanel = (props: BarChartProps): JSX.Element => { + const { theme, height, repoNames, currentRepo } = props; + const divEL = useRef(null); + const TH = theme == 'light' ? LIGHT_THEME : DARK_THEME; + const [data, setData] = useState<{ [repo: string]: RawRepoData }>({}); + + const option: echarts.EChartsOption = { + graphic: [ + { + type: 'text', + left: 'center', + top: 'center', + style: { + fill: '#333', + text: valueSum(data).toString(), + font: 'bold 48px Arial', + }, + }, + ], + }; + + useEffect(() => { + const fetchData = async () => { + for (const repo of repoNames) { + try { + //getStars() to fetch repository data + const starsData = await getStars(repo); + // Update Data/ + setData((prevData) => ({ ...prevData, [repo]: starsData })); + } catch (error) { + console.error(`Error fetching stars data for ${repo}:`, error); + // If the retrieval fails, set the data to an empty object + setData((prevData) => ({ ...prevData, [repo]: {} })); + } + } + }; + fetchData(); + }, []); + + useEffect(() => { + let chartDOM = divEL.current; + const TH = 'light' ? LIGHT_THEME : DARK_THEME; + + const instance = echarts.init(chartDOM as any); + instance.setOption(option); + instance.dispatchAction({ + type: 'highlight', + // seriesIndex: Number(currentRepo), + // dataIndex: Number(currentRepo), + name: repoNames[Number(currentRepo)], + seriesName: repoNames[Number(currentRepo)], + }); + return () => { + instance.dispose(); + }; + }, [data, currentRepo]); + + return
; +}; + +function valueSum(data: Record): number { + return Object.values(data).reduce((sum, repoData) => { + const lastData = generateDataByMonth(repoData).at(-1); + const value = + lastData !== undefined && lastData[0] === getNewestMonth() + ? lastData[1] + : 0; + return sum + value; + }, 0); +} + +export default NumericPanel; diff --git a/src/pages/ContentScripts/features/repo-collection/charts/PieChart.tsx b/src/pages/ContentScripts/features/repo-collection/charts/PieChart.tsx new file mode 100644 index 00000000..f8af46e1 --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/charts/PieChart.tsx @@ -0,0 +1,122 @@ +import React, { useEffect, useRef, useState } from 'react'; +import * as echarts from 'echarts'; +import generateDataByMonth from '../../../../../helpers/generate-data-by-month'; +import { getParticipant } from '../../../../../api/repo'; +import getNewestMonth from '../../../../../helpers/get-newest-month'; + +interface RawRepoData { + [date: string]: number; +} + +const LIGHT_THEME = { + FG_COLOR: '#24292f', + BG_COLOR: '#ffffff', + PALLET: ['#5470c6', '#91cc75'], +}; + +const DARK_THEME = { + FG_COLOR: '#c9d1d9', + BG_COLOR: '#0d1118', + PALLET: ['#58a6ff', '#3fb950'], +}; + +interface PieChartProps { + theme: 'light' | 'dark'; + height: number; + repoNames: string[]; + + currentRepo?: string; +} + +const PieChart = (props: PieChartProps): JSX.Element => { + const { theme, height, repoNames, currentRepo } = props; + const divEL = useRef(null); + const TH = theme == 'light' ? LIGHT_THEME : DARK_THEME; + const [data, setData] = useState<{ [repo: string]: RawRepoData }>({}); + + const option: echarts.EChartsOption = { + tooltip: { + trigger: 'item', + }, + legend: { + type: 'scroll', + }, + + series: [ + { + type: 'pie', + radius: ['40%', '70%'], + avoidLabelOverlap: false, + itemStyle: { + borderRadius: 10, + borderColor: '#fff', + borderWidth: 2, + }, + label: { + show: false, + position: 'center', + }, + emphasis: { + label: { + show: true, + fontSize: 20, + fontWeight: 'bold', + }, + }, + data: PieChartData(data), + }, + ], + }; + + useEffect(() => { + const fetchData = async () => { + for (const repo of repoNames) { + try { + //getStars() to fetch repository data + const starsData = await getParticipant(repo); + // Update Data + setData((prevData) => ({ ...prevData, [repo]: starsData })); + } catch (error) { + console.error(`Error fetching stars data for ${repo}:`, error); + // If the retrieval fails, set the data to an empty object + setData((prevData) => ({ ...prevData, [repo]: {} })); + } + } + }; + fetchData(); + }, []); + + useEffect(() => { + let chartDOM = divEL.current; + const TH = 'light' ? LIGHT_THEME : DARK_THEME; + + const instance = echarts.init(chartDOM as any); + instance.setOption(option); + console.log('pieChart,currentRepo', currentRepo); + instance.dispatchAction({ + type: 'highlight', + // seriesIndex: 0, + dataIndex: Number(currentRepo), + }); + return () => { + instance.dispose(); + }; + }, [data, currentRepo]); + + return
; +}; + +// Retrieve data for the current month +const PieChartData = (data: { [repo: string]: RawRepoData }) => + Object.entries(data).map(([repoName, repoData]) => { + const lastData = generateDataByMonth(repoData).at(-1); + return { + name: repoName, + value: + lastData !== undefined && lastData[0] === getNewestMonth() + ? lastData[1] + : 0, + }; + }); + +export default PieChart; diff --git a/src/pages/ContentScripts/features/repo-collection/charts/SankeyChart.tsx b/src/pages/ContentScripts/features/repo-collection/charts/SankeyChart.tsx new file mode 100644 index 00000000..02c15c1f --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/charts/SankeyChart.tsx @@ -0,0 +1,159 @@ +import React, { useEffect, useRef, useState } from 'react'; +import * as echarts from 'echarts'; +import getNewestMonth from '../../../../../helpers/get-newest-month'; +import { getActivityDetails } from '../../../../../api/repo'; + +const LIGHT_THEME = { + FG_COLOR: '#24292f', + BG_COLOR: '#ffffff', + PALLET: ['#5470c6', '#91cc75'], +}; + +const DARK_THEME = { + FG_COLOR: '#c9d1d9', + BG_COLOR: '#0d1118', + PALLET: ['#58a6ff', '#3fb950'], +}; + +interface SankeyChartProps { + theme: 'light' | 'dark'; + height: number; + repoNames: string[]; + + currentRepo?: string; +} + +const SankeyChart = (props: SankeyChartProps): JSX.Element => { + const { theme, height, repoNames, currentRepo } = props; + const divEL = useRef(null); + const [data, setData] = useState<{ [repoName: string]: RepoData }>({}); + + const option: echarts.EChartsOption = { + tooltip: { + trigger: 'item', + triggerOn: 'mousemove', + }, + // legend: { + // type: 'scroll', + // }, + animation: false, + grid: { + left: '2%', + right: '10%', + bottom: '3%', + // containLabel: true, + }, + series: [ + { + type: 'sankey', + // bottom: '10%', + emphasis: { + focus: 'adjacency', + }, + data: lastMonthData(data).nodes, + links: lastMonthData(data).links, + // orient: "vertical", + // label: { + // position: "top" + // }, + lineStyle: { + color: 'source', + curveness: 0.5, + }, + }, + ], + }; + + useEffect(() => { + const fetchData = async () => { + for (const repo of repoNames) { + try { + //getStars() to fetch repository data + const starsData = await getActivityDetails(repo); + // Update Data/ + setData((prevData) => ({ ...prevData, [repo]: starsData })); + } catch (error) { + console.error(`Error fetching stars data for ${repo}:`, error); + // If the retrieval fails, set the data to an empty object + setData((prevData) => ({ ...prevData, [repo]: {} })); + } + } + }; + fetchData(); + }, []); + + useEffect(() => { + let chartDOM = divEL.current; + const TH = 'light' ? LIGHT_THEME : DARK_THEME; + + const instance = echarts.init(chartDOM as any); + // console.log('sankeydata', data); + // console.log('lastMonthData', lastMonthData(data)); + instance.setOption(option); + instance.dispatchAction({ + type: 'highlight', + // seriesIndex: Number(currentRepo), + // dataIndex: Number(currentRepo), + name: repoNames[Number(currentRepo)], + seriesName: repoNames[Number(currentRepo)], + }); + return () => { + instance.dispose(); + }; + }, [data, currentRepo]); + + return
; +}; + +interface RepoData { + [month: string]: [string, number][]; +} + +interface DataNode { + name: string; +} + +interface DataLink { + source: string; + target: string; + value: number; +} + +interface LastMonthData { + nodes: DataNode[]; + links: DataLink[]; +} + +function lastMonthData(repo_data: { + [repoName: string]: RepoData; +}): LastMonthData { + const data: LastMonthData = { + nodes: [], + links: [], + }; + const userSet = new Set(); + const newestMonth = getNewestMonth(); + for (const [repoName, repoData] of Object.entries(repo_data)) { + const monthData = repoData[newestMonth]; + if (monthData) { + monthData.forEach(([userName, value]) => { + const link: DataLink = { + source: repoName, + target: userName, + value: value, + }; + userSet.add(userName); + data.links.push(link); + }); + } + } + data.nodes = [ + ...Object.keys(repo_data).map((repoName) => ({ name: repoName })), + ...Array.from(userSet).map((userName) => ({ name: userName })), + ]; + // console.log(data.nodes); + // console.log(data.links); + return data; +} + +export default SankeyChart; diff --git a/src/pages/ContentScripts/features/repo-collection/charts/StackedBarChart.tsx b/src/pages/ContentScripts/features/repo-collection/charts/StackedBarChart.tsx new file mode 100644 index 00000000..1ca420dd --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/charts/StackedBarChart.tsx @@ -0,0 +1,135 @@ +import React, { useEffect, useRef, useState } from 'react'; +import * as echarts from 'echarts'; +import generateDataByMonth from '../../../../../helpers/generate-data-by-month'; +import { getStars } from '../../../../../api/repo'; + +interface RawRepoData { + [date: string]: number; +} + +const LIGHT_THEME = { + FG_COLOR: '#24292f', + BG_COLOR: '#ffffff', + PALLET: ['#5470c6', '#91cc75'], +}; + +const DARK_THEME = { + FG_COLOR: '#c9d1d9', + BG_COLOR: '#0d1118', + PALLET: ['#58a6ff', '#3fb950'], +}; + +interface StackedBarChartProps { + theme: 'light' | 'dark'; + height: number; + repoNames: string[]; + + currentRepo?: string; +} + +const StackedBarChart = (props: StackedBarChartProps): JSX.Element => { + const { theme, height, repoNames, currentRepo } = props; + const divEL = useRef(null); + const TH = theme == 'light' ? LIGHT_THEME : DARK_THEME; + const [data, setData] = useState<{ [repo: string]: RawRepoData }>({}); + + const option: echarts.EChartsOption = { + tooltip: { + trigger: 'axis', + }, + legend: { + type: 'scroll', + }, + grid: { + left: '5%', + right: '4%', + bottom: '3%', + containLabel: true, + }, + xAxis: { + type: 'time', + splitLine: { + show: false, + }, + axisLabel: { + color: TH.FG_COLOR, + formatter: { + year: '{yearStyle|{yy}}', + month: '{MMM}', + }, + rich: { + yearStyle: { + fontWeight: 'bold', + }, + }, + }, + }, + yAxis: { + type: 'value', + }, + dataZoom: [ + { + type: 'inside', + start: 0, + end: 100, + minValueSpan: 3600 * 24 * 1000 * 180, + }, + ], + series: StarSeries(data), + }; + console.log('BarChartSeries??', StarSeries(data)); + useEffect(() => { + const fetchData = async () => { + for (const repo of repoNames) { + try { + const StarData = await getStars(repo); + setData((prevData) => ({ ...prevData, [repo]: StarData })); + } catch (error) { + console.error(`Error fetching stars data for ${repo}:`, error); + + setData((prevData) => ({ ...prevData, [repo]: {} })); + } + } + }; + fetchData(); + }, []); + // console.log('datatest', data); + useEffect(() => { + let chartDOM = divEL.current; + const TH = 'light' ? LIGHT_THEME : DARK_THEME; + + const instance = echarts.init(chartDOM as any); + instance.setOption(option); + instance.dispatchAction({ + type: 'highlight', + seriesIndex: Number(currentRepo), + dataIndex: Number(currentRepo), + name: repoNames[Number(currentRepo)], + seriesName: repoNames[Number(currentRepo)], + }); + return () => { + instance.dispose(); + }; + }, [data, currentRepo]); + + return
; +}; + +//Series:各仓库代码增加行数 +const StarSeries = (data: { + [repo: string]: RawRepoData; +}): echarts.SeriesOption[] => + Object.entries(data).map(([repoName, repoData]) => ({ + name: repoName, + type: 'bar', + stack: 'total', + // emphasis: emphasisStyle, + data: generateDataByMonth(repoData), + emphasis: { + focus: 'series', + }, + // yAxisIndex: 0, + triggerLineEvent: true, + })); + +export default StackedBarChart; From be37f282bf66cc31e0e782c2630eb6692d014506 Mon Sep 17 00:00:00 2001 From: Harry Date: Mon, 23 Oct 2023 22:10:37 -0400 Subject: [PATCH 05/10] chore: adjust styles --- .../repo-collection/CollectionContent/index.tsx | 4 ++-- .../repo-collection/CollectionModal/index.tsx | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.tsx index 62e74824..883a6c9e 100644 --- a/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.tsx +++ b/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.tsx @@ -36,7 +36,7 @@ const CollectionContent: React.FC = ({ repoNames, currentRepo }) => { }; return ( - + = ({ repoNames, currentRepo }) => { onClick={({ key }) => handleMenuClick(key)} //点击切换选中的repo /> - + diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.tsx index 72e22132..065e3810 100644 --- a/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.tsx +++ b/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.tsx @@ -35,7 +35,7 @@ export const CollectionManageModal = () => { const [isEdit, setIsEdit] = useState(); const editTab = ( -
+
+
From aa83c7e78a569f2f88149db01503d723912202cf Mon Sep 17 00:00:00 2001 From: wj23027 <809711241@qq.com> Date: Mon, 6 Nov 2023 22:36:27 +0800 Subject: [PATCH 07/10] feat: fixed the abnormal display issue and improved code structure in StackedBarChart. --- .../charts/StackedBarChart.tsx | 78 +++++++++++++------ 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/src/pages/ContentScripts/features/repo-collection/charts/StackedBarChart.tsx b/src/pages/ContentScripts/features/repo-collection/charts/StackedBarChart.tsx index 1ca420dd..8d4ae6df 100644 --- a/src/pages/ContentScripts/features/repo-collection/charts/StackedBarChart.tsx +++ b/src/pages/ContentScripts/features/repo-collection/charts/StackedBarChart.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import * as echarts from 'echarts'; import generateDataByMonth from '../../../../../helpers/generate-data-by-month'; import { getStars } from '../../../../../api/repo'; @@ -33,6 +33,26 @@ const StackedBarChart = (props: StackedBarChartProps): JSX.Element => { const TH = theme == 'light' ? LIGHT_THEME : DARK_THEME; const [data, setData] = useState<{ [repo: string]: RawRepoData }>({}); + // Fetch data for the specified repositories + useEffect(() => { + const fetchData = async () => { + const repoData: { [repo: string]: RawRepoData } = {}; + for (const repo of repoNames) { + try { + repoData[repo] = await getStars(repo); + } catch (error) { + console.error(`Error fetching stars data for ${repo}:`, error); + repoData[repo] = {}; + } + } + setData(repoData); + }; + fetchData(); + }, [repoNames]); + + // Preprocess the data + const preprocessedData = useMemo(() => addPreviousMonth(data), [data]); + const option: echarts.EChartsOption = { tooltip: { trigger: 'axis', @@ -75,28 +95,12 @@ const StackedBarChart = (props: StackedBarChartProps): JSX.Element => { minValueSpan: 3600 * 24 * 1000 * 180, }, ], - series: StarSeries(data), + series: StarSeries(preprocessedData), }; - console.log('BarChartSeries??', StarSeries(data)); - useEffect(() => { - const fetchData = async () => { - for (const repo of repoNames) { - try { - const StarData = await getStars(repo); - setData((prevData) => ({ ...prevData, [repo]: StarData })); - } catch (error) { - console.error(`Error fetching stars data for ${repo}:`, error); + //console.log('StackedBarChartseries', StarSeries(preprocessedData)); - setData((prevData) => ({ ...prevData, [repo]: {} })); - } - } - }; - fetchData(); - }, []); - // console.log('datatest', data); useEffect(() => { let chartDOM = divEL.current; - const TH = 'light' ? LIGHT_THEME : DARK_THEME; const instance = echarts.init(chartDOM as any); instance.setOption(option); @@ -110,25 +114,51 @@ const StackedBarChart = (props: StackedBarChartProps): JSX.Element => { return () => { instance.dispose(); }; - }, [data, currentRepo]); + }, [option, currentRepo]); return
; }; -//Series:各仓库代码增加行数 +// Preprocess data by adding previous month data +const addPreviousMonth = (data: { [repo: string]: RawRepoData }) => { + const preprocessedData: { [repo: string]: [string, number][] } = {}; + let maxLength = 0; + + // Iterate through the data of each repository + for (const [repoName, repoData] of Object.entries(data)) { + const generatedData = generateDataByMonth(repoData); + preprocessedData[repoName] = generatedData; + + // Update the maximum length + maxLength = Math.max(maxLength, generatedData.length); + } + // // Fill in arrays with months + for (const repoData of Object.values(preprocessedData)) { + while (repoData.length < maxLength) { + const [year, month] = repoData[0][0].split('-'); + const previousMonth = new Date(parseInt(year), parseInt(month) - 1, 1) + .toISOString() + .slice(0, 7); + repoData.unshift([previousMonth, 0]); + } + } + + return preprocessedData; +}; + +// Generate chart series for each repository const StarSeries = (data: { - [repo: string]: RawRepoData; + [repo: string]: [string, number][]; }): echarts.SeriesOption[] => Object.entries(data).map(([repoName, repoData]) => ({ name: repoName, type: 'bar', stack: 'total', // emphasis: emphasisStyle, - data: generateDataByMonth(repoData), + data: repoData, emphasis: { focus: 'series', }, - // yAxisIndex: 0, triggerLineEvent: true, })); From 59695b8d6f4fcc1bcb32be6e63fd1dba42c5c276 Mon Sep 17 00:00:00 2001 From: AndyHuang <763230412@qq.com> Date: Wed, 8 Nov 2023 11:47:11 +0800 Subject: [PATCH 08/10] feat: smoothly add and delete collections --- .../CollectionModal/CollectionEditor.tsx | 28 +++++----- .../repo-collection/CollectionModal/index.tsx | 53 ++++++++----------- 2 files changed, 36 insertions(+), 45 deletions(-) diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionModal/CollectionEditor.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionModal/CollectionEditor.tsx index 67dde62b..a387ac58 100644 --- a/src/pages/ContentScripts/features/repo-collection/CollectionModal/CollectionEditor.tsx +++ b/src/pages/ContentScripts/features/repo-collection/CollectionModal/CollectionEditor.tsx @@ -137,17 +137,19 @@ const CollectionEditor: React.FC = ({ } useEffect(() => { - Promise.all( - collectionData.map((repositoryName) => - fetchRepositoryDescription(repositoryName) + if (isEdit) { + Promise.all( + collectionData.map((repositoryName) => + fetchRepositoryDescription(repositoryName) + ) ) - ) - .then((repositoryDescriptions) => { - setDataSource(repositoryDescriptions); - }) - .catch((error) => { - console.error('Error fetching repository descriptions:', error); - }); + .then((repositoryDescriptions) => { + setDataSource(repositoryDescriptions); + }) + .catch((error) => { + console.error('Error fetching repository descriptions:', error); + }); + } }, []); const initialValues = { @@ -179,10 +181,11 @@ const CollectionEditor: React.FC = ({ .then((repoDescription) => { const key = dataSource ? dataSource.length + 1 : 1; repoDescription.key = key.toString(); - console.log('Repository Description:', repoDescription); + console.log('repoDescription', repoDescription); if (dataSource) { setDataSource([...dataSource, repoDescription]); } else { + console.log('repoDescription', repoDescription); setDataSource([repoDescription]); } }) @@ -215,7 +218,6 @@ const CollectionEditor: React.FC = ({ setDataSource(addKeyValue); } } catch (error) { - // 处理错误 console.error('Error:', error); } } @@ -302,7 +304,7 @@ const CollectionEditor: React.FC = ({
{ const [activeKey, setActiveKey] = useState(); const [items, setItems] = useState([]); - const [listData, setListData] = useState( - allRelations - .filter((relation) => relation.collectionId === allCollections[0].id) - .map((relation) => relation.repositoryId) - ); const [isClick, setIsClick] = useState(false); - const [isEdit, setIsEdit] = useState(); + const [isEdit, setIsEdit] = useState(false); const editTab = (
@@ -69,25 +64,20 @@ export const CollectionManageModal = () => { key: collection.id, }; }); - const initialListData = allRelations - .filter((relation) => - relation.collectionId === selectedCollection - ? selectedCollection - : allCollections[0].name - ) - .map((relation) => relation.repositoryId); + setActiveKey(selectedCollection); setItems(initialItems); - setListData(initialListData); }, [showManageModal]); - useEffect(() => {}, []); - const onCreate = async (values: any, newRepoData: string[]) => { if (isEdit) { const updatedItems = items.map((item) => { if (item.key === activeKey?.toString()) { - return { ...item, label: values.collectionName }; + return { + label: values.collectionName, + children: , + key: values.collectionName, + }; } return item; }); @@ -96,7 +86,7 @@ export const CollectionManageModal = () => { const newPanes = [...items]; newPanes.push({ label: values.collectionName, - children: , + children: , key: values.collectionName, }); setItems(newPanes); @@ -135,10 +125,9 @@ export const CollectionManageModal = () => { } console.log('Received values of form: ', values); - setListData(newRepoData); setSelectedCollection(values.collectionName); setIsClick(false); - setIsEdit(undefined); + setIsEdit(false); }; const onChange = (newActiveKey: string) => { @@ -151,8 +140,7 @@ export const CollectionManageModal = () => { title: 'Confirm Deletion', content: 'Are you sure you want to delete this collection?', okText: 'Confirm', - async onOk() { - // 用户点击确认按钮时执行的操作 + onOk() { let newActiveKey = activeKey; let lastIndex = -1; items.forEach((item, i) => { @@ -170,12 +158,7 @@ export const CollectionManageModal = () => { } setItems(newPanes); setActiveKey(newActiveKey); - await updaters.removeCollection(targetKey.toString()); - await updaters.removeRelations( - allRelations.filter( - (relation) => relation.collectionId === targetKey.toString() - ) - ); + updaters.removeCollection(targetKey.toString()); setSelectedCollection(newActiveKey); }, onCancel() {}, @@ -223,13 +206,19 @@ export const CollectionManageModal = () => { onCreate={onCreate} onCancel={() => { setIsClick(false); - setIsEdit(undefined); + setIsEdit(false); }} isEdit={isEdit} collectionName={selectedCollection ? selectedCollection : ''} - collectionData={allRelations - .filter((relation) => relation.collectionId === selectedCollection) - .map((relation) => relation.repositoryId)} + collectionData={ + isEdit + ? allRelations + .filter( + (relation) => relation.collectionId === selectedCollection + ) + .map((relation) => relation.repositoryId) + : [''] + } /> )}
From b32ece3e9cb867677f60e20e6137ca533af2aab9 Mon Sep 17 00:00:00 2001 From: Lam Tang Date: Fri, 10 Nov 2023 14:14:03 +0800 Subject: [PATCH 09/10] refactor: rename state name and discard unnecessary state --- .../CollectionButton/AddToCollections.tsx | 4 +- .../CollectionButton/CollectionList.tsx | 4 +- .../CollectionContent/index.tsx | 9 ++- .../repo-collection/CollectionModal/index.tsx | 73 +++++++++---------- .../features/repo-collection/context/index.ts | 6 +- 5 files changed, 49 insertions(+), 47 deletions(-) diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionButton/AddToCollections.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionButton/AddToCollections.tsx index fabacb6e..3aa1cf71 100644 --- a/src/pages/ContentScripts/features/repo-collection/CollectionButton/AddToCollections.tsx +++ b/src/pages/ContentScripts/features/repo-collection/CollectionButton/AddToCollections.tsx @@ -42,7 +42,7 @@ export const AddToCollections = () => { hideAddToCollections, setHideAddToCollections, setHideCollectionList, - setShowManageModal, + setShowCollectionModal, } = useRepoCollectionContext(); const [checkedCollectionIds, setCheckedCollectionIds] = useState< @@ -110,7 +110,7 @@ export const AddToCollections = () => { const manage = () => { // open modal to manage collections - setShowManageModal(true); + setShowCollectionModal(true); }; // if the ids of currentRepositoryCollections are the same as the ids of selectedCollectionIds, then the "Apply" button should be disabled diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionButton/CollectionList.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionButton/CollectionList.tsx index 73ece968..ca11cb77 100644 --- a/src/pages/ContentScripts/features/repo-collection/CollectionButton/CollectionList.tsx +++ b/src/pages/ContentScripts/features/repo-collection/CollectionButton/CollectionList.tsx @@ -34,12 +34,12 @@ export const CollectionList = () => { setHideAddToCollections, setHideCollectionList, setSelectedCollection, - setShowManageModal, + setShowCollectionModal, } = useRepoCollectionContext(); const handleCollectionClick = (collectionId: Collection['id']) => { setSelectedCollection(collectionId); - setShowManageModal(true); + setShowCollectionModal(true); }; const goToAddToCollections = () => { diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.tsx index 883a6c9e..dc577a25 100644 --- a/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.tsx +++ b/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.tsx @@ -36,7 +36,14 @@ const CollectionContent: React.FC = ({ repoNames, currentRepo }) => { }; return ( - + { const { - showManageModal, - setShowManageModal, + showCollectionModal, + setShowCollectionModal, selectedCollection, setSelectedCollection, updaters, @@ -26,25 +28,22 @@ export const CollectionManageModal = () => { const [activeKey, setActiveKey] = useState(); const [items, setItems] = useState([]); - const [isClick, setIsClick] = useState(false); - const [isEdit, setIsEdit] = useState(false); + const [isInEditMode, setIsInEditMode] = useState(false); const editTab = (
@@ -80,7 +82,10 @@ const CollectionDashboard: React.FC = ({ /> + +
+ = ({ /> + +
+ = ({ + @@ -163,7 +172,7 @@ const CollectionDashboard: React.FC = ({ - + ); }; diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.scss b/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.scss new file mode 100644 index 00000000..7d083f0a --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.scss @@ -0,0 +1,12 @@ +.ant-row { + margin-right: 0 !important; + margin-left: 0 !important; +} + +.ant-col:first-child { + padding-left: 0 !important; +} + +.ant-col:last-child { + padding-right: 0 !important; +} diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.tsx index dc577a25..81582afe 100644 --- a/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.tsx +++ b/src/pages/ContentScripts/features/repo-collection/CollectionContent/index.tsx @@ -39,7 +39,6 @@ const CollectionContent: React.FC = ({ repoNames, currentRepo }) => { = ({ repoNames, currentRepo }) => { onClick={({ key }) => handleMenuClick(key)} //点击切换选中的repo /> - + diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.scss b/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.scss new file mode 100644 index 00000000..17d9dca8 --- /dev/null +++ b/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.scss @@ -0,0 +1,3 @@ +.ant-tabs-content-holder { + overflow: auto; +} diff --git a/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.tsx b/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.tsx index fd0bcf3d..a1ca36f2 100644 --- a/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.tsx +++ b/src/pages/ContentScripts/features/repo-collection/CollectionModal/index.tsx @@ -2,10 +2,10 @@ import { useRepoCollectionContext } from '../context'; import CollectionEditor from './CollectionEditor'; import CollectionContent from '../CollectionContent'; -import React, { useState, useEffect } from 'react'; -import { Modal, Tabs, List, Col, Row, Button } from 'antd'; +import React, { useState } from 'react'; +import { Modal, Tabs, Button } from 'antd'; -const { TabPane } = Tabs; +import './index.scss'; type TargetKey = React.MouseEvent | React.KeyboardEvent | string; @@ -15,7 +15,7 @@ type CollectionTabType = { key: string; }; -export const CollectionManageModal = () => { +export const CollectionModal = () => { const { showCollectionModal, setShowCollectionModal, @@ -52,22 +52,6 @@ export const CollectionManageModal = () => { ); - useEffect(() => { - const initialItems = allCollections.map((collection) => { - const repoList = allRelations - .filter((relation) => relation.collectionId === collection.name) - .map((relation) => relation.repositoryId); - return { - label: collection.name, - children: , - key: collection.id, - }; - }); - - setActiveKey(selectedCollection); - setItems(initialItems); - }, [showCollectionModal]); - const onCreate = async (values: any, newRepoData: string[]) => { if (isInEditMode) { const updatedItems = items.map((item) => { @@ -170,6 +154,17 @@ export const CollectionManageModal = () => { if (action === 'remove') remove(targetKey); }; + const tabItems = allCollections.map((collection) => { + const repoList = allRelations + .filter((relation) => relation.collectionId === collection.name) + .map((relation) => relation.repositoryId); + return { + label: collection.name, + children: , + key: collection.id, + }; + }); + return (
{ onChange={onChange} activeKey={activeKey} onEdit={onEdit} - items={items} + items={tabItems} tabBarExtraContent={editTab} - style={{ height: '100%', margin: '0px 24px' }} + style={{ height: '100%', marginRight: '24px' }} /> { return ( - + ); };