From e9f8b027fa168f6669edfcf9d1cfceb353fb0820 Mon Sep 17 00:00:00 2001 From: Ted <406419317@qq.com> Date: Tue, 30 Jun 2026 20:12:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E8=87=AA=E5=B7=B1?= =?UTF-8?q?=E7=9A=84=E7=83=AD=E5=8A=9B=E5=9B=BE=E7=BB=84=E4=BB=B6=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E6=8D=A2=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 539 ------------------- package.json | 3 +- src/announcements/changelog_zh.md | 2 +- src/pages/sidebar/SideBarContent.tsx | 4 +- src/pages/sidebar/heatmap/CustomHeatmap.tsx | 316 +++++++++++ src/pages/sidebar/heatmap/custom-heatmap.css | 216 ++++++++ styles.css | 1 + 7 files changed, 537 insertions(+), 544 deletions(-) create mode 100644 src/pages/sidebar/heatmap/CustomHeatmap.tsx create mode 100644 src/pages/sidebar/heatmap/custom-heatmap.css diff --git a/package-lock.json b/package-lock.json index 023e063..dce7287 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "1.4.4", "license": "GPL-3.0-only", "dependencies": { - "cal-heatmap": "^4.2.4", "react": "^19.1.0", "react-calendar-heatmap": "^1.10.0", "react-dom": "^19.1.0", @@ -1747,20 +1746,6 @@ "node": ">= 8" } }, - "node_modules/@observablehq/plot": { - "version": "0.6.17", - "resolved": "https://registry.npmmirror.com/@observablehq/plot/-/plot-0.6.17.tgz", - "integrity": "sha512-/qaXP/7mc4MUS0s4cPPFASDRjtsWp85/TbfsciqDgU1HwYixbSbbytNuInD8AcTYC3xaxACgVX06agdfQy9W+g==", - "license": "ISC", - "dependencies": { - "d3": "^7.9.0", - "interval-tree-1d": "^1.0.0", - "isoformat": "^0.2.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1785,16 +1770,6 @@ "url": "https://opencollective.com/pkgr" } }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/@sinclair/typebox": { "version": "0.34.35", "resolved": "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.34.35.tgz", @@ -2721,12 +2696,6 @@ "dev": true, "license": "MIT" }, - "node_modules/binary-search-bounds": { - "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz", - "integrity": "sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==", - "license": "MIT" - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2827,23 +2796,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cal-heatmap": { - "version": "4.2.4", - "resolved": "https://registry.npmmirror.com/cal-heatmap/-/cal-heatmap-4.2.4.tgz", - "integrity": "sha512-TTNoQTRxHXrttOEbkraKv9vy2VpfQIwVLQJTlAfcBusQK9qrBL/UBO+WloAxv2yrR+P8URA2cuXEdc5iztER9g==", - "license": "MIT", - "dependencies": { - "@observablehq/plot": "^0.6.0", - "@popperjs/core": "^2.11.6", - "d3-color": "^3.1.0", - "d3-fetch": "^3.0.1", - "d3-selection": "^3.0.0", - "d3-transition": "^3.0.1", - "dayjs": "^1.11.7", - "eventemitter3": "^5.0.0", - "lodash-es": "^4.17.21" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", @@ -3034,15 +2986,6 @@ "dev": true, "license": "MIT" }, - "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", @@ -3079,413 +3022,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/d3": { - "version": "7.9.0", - "resolved": "https://registry.npmmirror.com/d3/-/d3-7.9.0.tgz", - "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", - "license": "ISC", - "dependencies": { - "d3-array": "3", - "d3-axis": "3", - "d3-brush": "3", - "d3-chord": "3", - "d3-color": "3", - "d3-contour": "4", - "d3-delaunay": "6", - "d3-dispatch": "3", - "d3-drag": "3", - "d3-dsv": "3", - "d3-ease": "3", - "d3-fetch": "3", - "d3-force": "3", - "d3-format": "3", - "d3-geo": "3", - "d3-hierarchy": "3", - "d3-interpolate": "3", - "d3-path": "3", - "d3-polygon": "3", - "d3-quadtree": "3", - "d3-random": "3", - "d3-scale": "4", - "d3-scale-chromatic": "3", - "d3-selection": "3", - "d3-shape": "3", - "d3-time": "3", - "d3-time-format": "4", - "d3-timer": "3", - "d3-transition": "3", - "d3-zoom": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-axis": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-brush": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "3", - "d3-transition": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-chord": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "license": "ISC", - "dependencies": { - "d3-path": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-contour": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/d3-contour/-/d3-contour-4.0.2.tgz", - "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "license": "ISC", - "dependencies": { - "d3-array": "^3.2.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmmirror.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "license": "ISC", - "dependencies": { - "delaunator": "5" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dsv": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "license": "ISC", - "dependencies": { - "commander": "7", - "iconv-lite": "0.6", - "rw": "1" - }, - "bin": { - "csv2json": "bin/dsv2json.js", - "csv2tsv": "bin/dsv2dsv.js", - "dsv2dsv": "bin/dsv2dsv.js", - "dsv2json": "bin/dsv2json.js", - "json2csv": "bin/json2dsv.js", - "json2dsv": "bin/json2dsv.js", - "json2tsv": "bin/json2dsv.js", - "tsv2csv": "bin/dsv2dsv.js", - "tsv2json": "bin/dsv2json.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "license": "ISC", - "dependencies": { - "d3-dsv": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-geo": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/d3-geo/-/d3-geo-3.1.1.tgz", - "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2.5.0 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-hierarchy": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "license": "ISC", - "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-interpolate": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "license": "ISC", - "dependencies": { - "d3-path": "^3.1.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "license": "ISC", - "dependencies": { - "d3-time": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" - } - }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "license": "MIT" - }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", @@ -3537,15 +3073,6 @@ "node": ">=0.10.0" } }, - "node_modules/delaunator": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/delaunator/-/delaunator-5.0.1.tgz", - "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", - "license": "ISC", - "dependencies": { - "robust-predicates": "^3.0.2" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3950,12 +3477,6 @@ "node": ">=0.10.0" } }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" - }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz", @@ -4420,18 +3941,6 @@ "node": ">=10.17.0" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", @@ -4509,24 +4018,6 @@ "dev": true, "license": "ISC" }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/interval-tree-1d": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/interval-tree-1d/-/interval-tree-1d-1.0.4.tgz", - "integrity": "sha512-wY8QJH+6wNI0uh4pDQzMvl+478Qh7Rl4qLmqiluxALlNvl+I+o5x38Pw3/z7mDPTPS1dQalZJXsmbvxx5gclhQ==", - "license": "MIT", - "dependencies": { - "binary-search-bounds": "^2.0.0" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -4618,12 +4109,6 @@ "dev": true, "license": "ISC" }, - "node_modules/isoformat": { - "version": "0.2.1", - "resolved": "https://registry.npmmirror.com/isoformat/-/isoformat-0.2.1.tgz", - "integrity": "sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==", - "license": "ISC" - }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmmirror.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -5460,12 +4945,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmmirror.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -6267,12 +5746,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", - "license": "Unlicense" - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", @@ -6297,18 +5770,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rw": { - "version": "1.3.3", - "resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", - "license": "BSD-3-Clause" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.26.0.tgz", diff --git a/package.json b/package.json index bdf5f76..b486d08 100644 --- a/package.json +++ b/package.json @@ -30,11 +30,10 @@ "typescript": "4.7.4" }, "dependencies": { - "cal-heatmap": "^4.2.4", "react": "^19.1.0", "react-calendar-heatmap": "^1.10.0", "react-dom": "^19.1.0", "react-tooltip": "^5.28.1", "zustand": "^5.0.4" } -} \ No newline at end of file +} diff --git a/src/announcements/changelog_zh.md b/src/announcements/changelog_zh.md index f8cea66..8d4e680 100644 --- a/src/announcements/changelog_zh.md +++ b/src/announcements/changelog_zh.md @@ -12,7 +12,7 @@ - 筛选某一天时,筛选条件显示当天日期,而不是日期范围。 - 筛选结果中的标签,现在可以点击。 - 更改筛选方案现在会把条件同步到搜索框,现在可以基于当前筛选方案进行个性搜索。 - +- 更换热力图组件,现在可以查看所有日期的情况。 ### 1.4.4 diff --git a/src/pages/sidebar/SideBarContent.tsx b/src/pages/sidebar/SideBarContent.tsx index 60b0018..241dcff 100644 --- a/src/pages/sidebar/SideBarContent.tsx +++ b/src/pages/sidebar/SideBarContent.tsx @@ -1,4 +1,4 @@ -import { Heatmap } from 'src/pages/sidebar/heatmap/Heatmap'; +import { CustomHeatmap } from 'src/pages/sidebar/heatmap/CustomHeatmap'; import { FilterSchemesInfo } from './filterScheme/FilterSchemesInfo'; import { ViewSchemesInfo } from './viewScheme/ViewSchemesInfo'; import { RandomReviewInfo } from './randomReview/RandomReviewInfo'; @@ -22,7 +22,7 @@ export const SidebarContent = () => { return (
- +
void, + cellSize?: number, + gapSize?: number, + fontSize?: number, +}) => { + const sortType = useCombineStore((state) => state.appData.sortType); + const allFiles = useCombineStore((state) => state.allFiles); + const firstUseDateStr = useCombineStore((state) => state.appData.firstUseDate); + + const values = useMemo(() => getHeatmapValues(allFiles, sortType), [allFiles, sortType]); + + const valueMap = useMemo(() => { + const map = new Map(); + values.forEach(v => { + map.set(v.date, v.count); + }); + return map; + }, [values]); + + const today = useMemo(() => { + const d = new Date(); + d.setHours(0, 0, 0, 0); + return d; + }, []); + + const earliestDate = useMemo(() => { + // Default to 1 year ago (52 weeks ago) + let d = new Date(today); + d.setFullYear(today.getFullYear() - 1); + + // Find the earliest file date in values + if (values && values.length > 0) { + values.forEach(v => { + const dateVal = new Date(v.date); + if (!isNaN(dateVal.getTime()) && dateVal < d) { + d = dateVal; + } + }); + } + + // Check first use date + if (firstUseDateStr) { + const fud = new Date(firstUseDateStr); + if (!isNaN(fud.getTime()) && fud < d) { + d = fud; + } + } + return d; + }, [values, firstUseDateStr, today]); + + // Align to Sunday of start week + const startOfWeek = useMemo(() => { + const d = new Date(earliestDate); + const day = d.getDay(); + d.setDate(d.getDate() - day); + d.setHours(0, 0, 0, 0); + return d; + }, [earliestDate]); + + // Align to Saturday of end week + const endOfWeek = useMemo(() => { + const d = new Date(today); + const day = d.getDay(); + d.setDate(d.getDate() + (6 - day)); + d.setHours(23, 59, 59, 999); + return d; + }, [today]); + + // Generate weeks array + const weeks = useMemo(() => { + const result: { + date: Date; + dateStr: string; + count: number; + level: number; + isToday: boolean; + isFuture: boolean; + tooltipText: string; + }[][] = []; + + const currentDay = new Date(startOfWeek); + let currentWeek: typeof result[0] = []; + + const getDayLevel = (count: number) => { + if (count === 0) return 0; + const numPerLevel = 4; + const numOflevels = 3; + return Math.min(numOflevels, Math.ceil(count / numPerLevel)); + }; + + const todayStr = moment(today).format('YYYY-MM-DD'); + + while (currentDay <= endOfWeek) { + const dateStr = moment(currentDay).format('YYYY-MM-DD'); + const count = valueMap.get(dateStr) || 0; + const level = getDayLevel(count); + const isToday = dateStr === todayStr; + const isFuture = currentDay > today; + + const activityText = (sortType === 'created' || sortType === 'earliestCreated') + ? i18n.t('notes_created_at') + : i18n.t('notes_modified_at'); + const tooltipText = count > 0 + ? `${count} ${activityText} ${dateStr}` + : `${i18n.t('empty_note_result')} ${dateStr}`; + + currentWeek.push({ + date: new Date(currentDay), + dateStr, + count, + level: isFuture ? 0 : level, + isToday, + isFuture, + tooltipText, + }); + + if (currentWeek.length === 7) { + result.push(currentWeek); + currentWeek = []; + } + + currentDay.setDate(currentDay.getDate() + 1); + } + + return result; + }, [startOfWeek, endOfWeek, valueMap, today, sortType]); + + // Precalculate which week columns should render a month label to avoid collisions + const labelIndices = useMemo(() => { + const indices = new Set(); + let lastLabelIndex = -10; + + for (let i = 0; i < weeks.length; i++) { + const week = weeks[i]; + const prevWeek = weeks[i - 1]; + const isNewMonth = i === 0 || week[0].date.getMonth() !== prevWeek[0].date.getMonth(); + + if (isNewMonth) { + // If it's the very first column, check if the next month is too close (within 3 columns) + if (i === 0) { + let nextMonthIndex = -1; + for (let j = 1; j < weeks.length; j++) { + if (weeks[j][0].date.getMonth() !== week[0].date.getMonth()) { + nextMonthIndex = j; + break; + } + } + // Skip the very first month label if it's too short, prioritizing the next month's clean render + if (nextMonthIndex !== -1 && nextMonthIndex < 3) { + continue; + } + } + + // Spacing check: Only render month label if it's at least 3 columns away from the last rendered label + if (i - lastLabelIndex >= 3) { + indices.add(i); + lastLabelIndex = i; + } + } + } + return indices; + }, [weeks]); + + // Find the first index that displays a month label + const firstLabelIndex = useMemo(() => { + let min = Infinity; + labelIndices.forEach(idx => { + if (idx < min) min = idx; + }); + return min === Infinity ? -1 : min; + }, [labelIndices]); + + // Format the month label: January displays ONLY the year, first label displays Year + Month + const getMonthLabelText = (date: Date, isFirst: boolean) => { + const monthIndex = date.getMonth(); + const monthName = i18n.t(`month${monthIndex + 1}` as any); + const year = date.getFullYear(); + const isJanuary = monthIndex === 0; + + if (isJanuary) { + return `${year}`; + } + + if (isFirst) { + return `${monthName}`; + } + return monthName; + }; + + // Scroll control ref + const containerRef = useRef(null); + const hasScrolledRef = useRef(false); + + // Initial scroll to today (far right) + useEffect(() => { + if (containerRef.current && !hasScrolledRef.current && weeks.length > 0) { + containerRef.current.scrollLeft = containerRef.current.scrollWidth; + hasScrolledRef.current = true; + } + }, [weeks.length]); + + // Mouse drag-to-scroll panning state + const [isDragging, setIsDragging] = useState(false); + const [startX, setStartX] = useState(0); + const [scrollLeftState, setScrollLeftState] = useState(0); + + const handleMouseDown = (e: React.MouseEvent) => { + if (!containerRef.current) return; + setIsDragging(true); + setStartX(e.pageX - containerRef.current.offsetLeft); + setScrollLeftState(containerRef.current.scrollLeft); + }; + + const handleMouseMove = (e: React.MouseEvent) => { + if (!isDragging || !containerRef.current) return; + e.preventDefault(); + const x = e.pageX - containerRef.current.offsetLeft; + // Sensitivity factor: 1.5 + const walk = (x - startX) * 1.5; + containerRef.current.scrollLeft = scrollLeftState - walk; + }; + + const handleMouseUpOrLeave = () => { + setIsDragging(false); + }; + + return ( +
+ + {/* Scrollable Grid */} +
+
+ {/* Month Labels Row */} +
+ {weeks.map((week, index) => { + const showLabel = labelIndices.has(index); + const isFirst = index === firstLabelIndex; + return ( +
+ {showLabel && ( + + {getMonthLabelText(week[0].date, isFirst)} + + )} +
+ ); + })} +
+ + {/* Day Grid Row */} +
+ {weeks.map((week, index) => ( +
+ {week.map((day) => ( +
!day.isFuture && onCickDate(day.dateStr)} + /> + ))} +
+ ))} +
+
+
+ + +
+ ); +}; + +export const getHeatmapValues = (fileInfos: FileInfo[], sortType: SortType) => { + const valueMap = fileInfos + .map(f => f.file.stat) + .map(stat => { + const timestamp = (sortType === 'created' || sortType === 'earliestCreated') ? stat.ctime : stat.mtime; + return moment(timestamp).format('YYYY-MM-DD'); + }) + .reduce>( + (pre, cur) => pre.set(cur, pre.has(cur) ? pre.get(cur)! + 1 : 1), + new Map()); + return Array + .from(valueMap.entries()) + .map(([key, value]) => { + return { date: key, count: value }; + }); +}; diff --git a/src/pages/sidebar/heatmap/custom-heatmap.css b/src/pages/sidebar/heatmap/custom-heatmap.css new file mode 100644 index 0000000..5277459 --- /dev/null +++ b/src/pages/sidebar/heatmap/custom-heatmap.css @@ -0,0 +1,216 @@ +/* Container for the whole Heatmap widget */ +.custom-heatmap-container { + display: flex; + flex-direction: column; + width: 100%; +} + +/* Header style containing title and controls */ +.custom-heatmap-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; + padding: 0 4px; +} + +.custom-heatmap-title { + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.custom-heatmap-controls { + display: flex; + align-items: center; + gap: 4px; +} + +/* Small elegant navigation buttons */ +.custom-heatmap-control-btn { + background: var(--background-secondary); + border: 1px solid var(--border-color, var(--background-modifier-border)); + color: var(--text-normal); + border-radius: 4px; + padding: 2px 6px; + font-size: 10px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.15s ease, border-color 0.15s ease; + height: 18px; +} + +.custom-heatmap-control-btn:hover { + background: var(--background-modifier-hover); + border-color: var(--text-muted); +} + +.custom-heatmap-control-btn svg { + stroke: var(--text-muted); +} + +.custom-heatmap-control-btn:hover svg { + stroke: var(--text-normal); +} + +/* Scrollable area */ +.custom-heatmap-scroll-container { + overflow-x: auto; + width: 100%; + cursor: grab; + user-select: none; + -webkit-user-select: none; + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb-bg, var(--background-modifier-border)) transparent; + padding-bottom: 4px; +} + +.custom-heatmap-scroll-container.is-dragging { + cursor: grabbing; +} + +/* We hide standard scrollbars on Webkit browsers for a cleaner look */ +.custom-heatmap-scroll-container::-webkit-scrollbar { + height: 4px; +} + +.custom-heatmap-scroll-container::-webkit-scrollbar-track { + background: transparent; +} + +.custom-heatmap-scroll-container::-webkit-scrollbar-thumb { + background-color: var(--scrollbar-thumb-bg, var(--background-modifier-border)); + border-radius: 4px; +} + +/* Wrapper to hold row sizes together */ +.custom-heatmap-content-wrapper { + display: inline-flex; + flex-direction: column; + padding: 4px; + min-width: max-content; +} + +/* Month labels row layout */ +.custom-heatmap-months-row { + display: flex; + height: 14px; + margin-bottom: 4px; +} + +.custom-heatmap-month-label-col { + position: relative; +} + +.custom-heatmap-month-label { + position: absolute; + left: 0; + top: 0; + color: var(--text-muted); + white-space: nowrap; + line-height: 1; +} + +/* Days grid layout */ +.custom-heatmap-grid-row { + display: flex; +} + +.custom-heatmap-week-column { + display: flex; + flex-direction: column; +} + +/* Day cell styling */ +.custom-heatmap-day-cell { + border-radius: 2px; + transition: transform 0.1s ease, box-shadow 0.15s ease; + cursor: pointer; + position: relative; +} + +/* Prevent clicking future days */ +.custom-heatmap-day-cell.is-future { + cursor: default; + opacity: 0.3; +} + +/* Highlight today with a subtle border/outline */ +.custom-heatmap-day-cell.is-today { + outline: 1.5px solid var(--color-accent); + outline-offset: 1px; +} + +/* Hover effects for interactive cells */ +.custom-heatmap-day-cell:not(.is-future):hover { + transform: scale(1.3); + z-index: 10; + box-shadow: 0 0 6px var(--color-accent); +} + +/* Color scales mapped exactly as requested */ +.custom-heatmap-day-cell.color-scale-0 { + background-color: var(--background-primary-alt); +} + +.custom-heatmap-day-cell.color-scale-1 { + background-color: color-mix(in srgb, var(--color-accent) 33%, var(--background-primary-alt) 66%); +} + +.custom-heatmap-day-cell.color-scale-2 { + background-color: color-mix(in srgb, var(--color-accent) 66%, var(--background-primary-alt) 33%); +} + +.custom-heatmap-day-cell.color-scale-3 { + background-color: var(--color-accent); +} + +/* Footer / Legend */ +.custom-heatmap-footer { + display: flex; + justify-content: flex-end; + align-items: center; + margin-top: 6px; + padding: 0 4px; +} + +.custom-heatmap-legend { + display: flex; + align-items: center; + gap: 3px; +} + +.custom-heatmap-legend-text { + color: var(--text-muted); +} + +.custom-heatmap-legend-cell { + border-radius: 2px; +} + +.custom-heatmap-legend-cell.color-scale-0 { + background-color: var(--background-primary-alt); +} + +.custom-heatmap-legend-cell.color-scale-1 { + background-color: color-mix(in srgb, var(--color-accent) 33%, var(--background-primary-alt) 66%); +} + +.custom-heatmap-legend-cell.color-scale-2 { + background-color: color-mix(in srgb, var(--color-accent) 66%, var(--background-primary-alt) 33%); +} + +.custom-heatmap-legend-cell.color-scale-3 { + background-color: var(--color-accent); +} + +/* Tooltip layering */ +.custom-heatmap-tooltip { + z-index: 2001; + font-size: 10px !important; + padding: 4px 8px !important; + border-radius: 4px !important; +} diff --git a/styles.css b/styles.css index 421dd66..af01263 100644 --- a/styles.css +++ b/styles.css @@ -4,6 +4,7 @@ @import 'src/pages/sidebar/sidebar.css'; @import 'src/pages/sidebar/heatmap/heatmap.css'; +@import 'src/pages/sidebar/heatmap/custom-heatmap.css'; @import 'src/pages/sidebar/filterScheme/filterScheme.css'; @import 'src/pages/sidebar/viewScheme/viewScheme.css'; @import 'src/pages/sidebar/randomReview/randomReview.css';