feat: Add 3D activity heatmap component using D3 to visualize combined GitHub and Gitea contributions, accessible via a new tab.
This commit is contained in:
426
package-lock.json
generated
426
package-lock.json
generated
@@ -11,6 +11,7 @@
|
||||
"@react-three/drei": "^10.7.7",
|
||||
"@react-three/fiber": "^9.4.2",
|
||||
"@studio-freight/lenis": "^1.0.42",
|
||||
"d3": "^7.9.0",
|
||||
"d3-geo": "^3.1.1",
|
||||
"gsap": "^3.14.2",
|
||||
"react": "^19.2.0",
|
||||
@@ -1959,6 +1960,47 @@
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/d3": {
|
||||
"version": "7.9.0",
|
||||
"resolved": "https://registry.npmjs.org/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.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
@@ -1971,6 +2013,176 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-axis": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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-dsv/node_modules/commander": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-ease": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
|
||||
@@ -1983,6 +2195,181 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-hierarchy": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
@@ -2008,6 +2395,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/delaunator": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
|
||||
"integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"robust-predicates": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-gpu": {
|
||||
"version": "5.0.70",
|
||||
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz",
|
||||
@@ -2471,6 +2867,18 @@
|
||||
"integrity": "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/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/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
@@ -3063,6 +3471,12 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/robust-predicates": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
|
||||
"integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==",
|
||||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.53.4",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.4.tgz",
|
||||
@@ -3105,6 +3519,18 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rw": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/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.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"@react-three/drei": "^10.7.7",
|
||||
"@react-three/fiber": "^9.4.2",
|
||||
"@studio-freight/lenis": "^1.0.42",
|
||||
"d3": "^7.9.0",
|
||||
"d3-geo": "^3.1.1",
|
||||
"gsap": "^3.14.2",
|
||||
"react": "^19.2.0",
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useRef } from 'react';
|
||||
import Header from './components/Header';
|
||||
import HeroModel from './canvas/HeroModel';
|
||||
import ProductGrid from './components/ProductGrid';
|
||||
import WhereAmI from './components/WhereAmI';
|
||||
import InfoTabs from './components/InfoTabs';
|
||||
import Footer from './components/Footer';
|
||||
import './styles/index.css';
|
||||
import gsap from 'gsap';
|
||||
@@ -110,7 +110,7 @@ function App() {
|
||||
|
||||
{/* Product Grid Section */}
|
||||
<ProductGrid />
|
||||
<WhereAmI />
|
||||
<InfoTabs />
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
|
||||
276
src/components/ActivityHeatmap.jsx
Normal file
276
src/components/ActivityHeatmap.jsx
Normal file
@@ -0,0 +1,276 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import * as d3 from 'd3';
|
||||
|
||||
const ActivityHeatmap = () => {
|
||||
const svgRef = useRef(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [data, setData] = useState([]);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
// 1. Fetch GitHub Data (using proxy)
|
||||
const githubRes = await fetch('https://github-contributions-api.jogruber.de/v4/MatissJurevics');
|
||||
const githubJson = await githubRes.json();
|
||||
|
||||
// 2. Fetch Gitea Data
|
||||
const giteaRes = await fetch('/api/gitea/api/v1/users/Matiss/heatmap');
|
||||
const giteaJson = await giteaRes.json(); // Array of { timestamp, contributions }
|
||||
|
||||
// 3. Process & Merge
|
||||
const processData = () => {
|
||||
const merged = new Map();
|
||||
|
||||
// Initialize with GitHub data (usually last year)
|
||||
// The proxy returns 'contributions' array for the last year usually?
|
||||
// Actually correct structure from jogruber api is { total: {}, contributions: [ { date, count, level } ] }
|
||||
// But let's check what it returns specifically or handle 'years' object.
|
||||
// Usually structure val: { yearly: [] , total: {} }
|
||||
|
||||
// Let's rely on standard logic: get last 365 days.
|
||||
const today = new Date();
|
||||
const oneYearAgo = new Date();
|
||||
oneYearAgo.setDate(today.getDate() - 365);
|
||||
|
||||
// Helper to normalize date string YYYY-MM-DD
|
||||
const toKey = (date) => date.toISOString().split('T')[0];
|
||||
|
||||
// Initialize map with empty days
|
||||
for (let d = new Date(oneYearAgo); d <= today; d.setDate(d.getDate() + 1)) {
|
||||
merged.set(toKey(d), { date: new Date(d), github: 0, gitea: 0 });
|
||||
}
|
||||
|
||||
// Fill GitHub
|
||||
// The API usually returns 'contributions' list.
|
||||
// If structure is complex, we might need adjustments, but let's assume flat list available or extractable.
|
||||
// Actually, jogruber V4 returns: { total: {}, contributions: [ { date, count, level } ... ] }
|
||||
if (githubJson.contributions) {
|
||||
githubJson.contributions.forEach(day => {
|
||||
if (merged.has(day.date)) {
|
||||
const curr = merged.get(day.date);
|
||||
curr.github = day.count;
|
||||
merged.set(day.date, curr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fill Gitea
|
||||
// Gitea heatmap endpoint returns array of { timestamp: unix_timestamp, contributions: count }
|
||||
if (Array.isArray(giteaJson)) {
|
||||
giteaJson.forEach(item => {
|
||||
const d = new Date(item.timestamp * 1000);
|
||||
const key = toKey(d);
|
||||
if (merged.has(key)) {
|
||||
const curr = merged.get(key);
|
||||
curr.gitea = item.contributions;
|
||||
merged.set(key, curr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Array.from(merged.values());
|
||||
};
|
||||
|
||||
const processed = processData();
|
||||
setData(processed);
|
||||
} catch (err) {
|
||||
console.error("Error fetching activity:", err);
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading || !data.length || !svgRef.current) return;
|
||||
|
||||
// D3 Drawing Logic
|
||||
const width = 1000;
|
||||
const height = 600;
|
||||
const svg = d3.select(svgRef.current);
|
||||
svg.selectAll("*").remove();
|
||||
|
||||
const g = svg.append("g")
|
||||
.attr("transform", `translate(${width / 2}, ${height / 4})`); // Center top-ish
|
||||
|
||||
// Isometric projection
|
||||
// x grid runs diagonally right-down, y grid runs diagonally left-down
|
||||
const tileWidth = 12;
|
||||
const tileHeight = 7; // flattened appearance
|
||||
|
||||
// We need to organize data into weeks (x) and days of week (y)
|
||||
// Similar to GitHub's contribution graph but 3D
|
||||
// y: 0 (Sunday) - 6 (Saturday)
|
||||
// x: Week index 0 - 52
|
||||
|
||||
const mappedData = data.map((d, i) => {
|
||||
const dayOfWeek = d.date.getDay(); // 0-6
|
||||
// Determine week index relative to start
|
||||
const weekIndex = Math.floor(i / 7);
|
||||
return {
|
||||
...d,
|
||||
gridX: weekIndex,
|
||||
gridY: dayOfWeek
|
||||
};
|
||||
});
|
||||
|
||||
// Projection functions
|
||||
// Iso 30 deg: x' = (col - row) * w, y' = (col + row) * h/2
|
||||
const project = (col, row) => {
|
||||
return {
|
||||
x: (col - row) * tileWidth,
|
||||
y: (col + row) * tileHeight
|
||||
};
|
||||
};
|
||||
|
||||
// Color scales
|
||||
const maxVal = d3.max(mappedData, d => d.github + d.gitea) || 5;
|
||||
const heightScale = d3.scaleLinear().domain([0, maxVal]).range([0, 50]);
|
||||
|
||||
// Colors
|
||||
const githubColor = "#2da44e"; // GitHub Green
|
||||
const giteaColor = "#609926"; // Gitea Green (slightly different, maybe orangey for contrast?)
|
||||
// Let's use user's theme color for Gitea to contrast? User said "git tea", maybe stick to green varieties or separate?
|
||||
// User asked for "stacked", so distinct colors helpful.
|
||||
// Let's use Theme Orange (#ff4d00) for Gitea to match site, and GitHub Green for GitHub.
|
||||
const colorGithub = "#2da44e";
|
||||
const colorGitea = "#ff4d00";
|
||||
|
||||
// Draw standard floor tiles first (for context)
|
||||
// Only needed if we want a "grid" look. Let's skip empty tiles for performance/cleanliness or draw dark base.
|
||||
|
||||
// Sort by gridY then gridX to render back-to-front correctly for painter's algorithm
|
||||
// Render order: smallest y+x (back) to largest y+x (front)
|
||||
// Actually for isometric:
|
||||
// We want to draw cols (weeks) from left to right?
|
||||
// Let's sort by sum of coords for simple stacking.
|
||||
mappedData.sort((a, b) => (a.gridX + a.gridY) - (b.gridX + b.gridY));
|
||||
|
||||
mappedData.forEach(d => {
|
||||
if (d.github === 0 && d.gitea === 0) {
|
||||
// Draw faint base tile
|
||||
const pos = project(d.gridX, d.gridY);
|
||||
// Draw a simple diamond path
|
||||
const path = `M${pos.x} ${pos.y}
|
||||
L${pos.x + tileWidth} ${pos.y + tileHeight}
|
||||
L${pos.x} ${pos.y + 2 * tileHeight}
|
||||
L${pos.x - tileWidth} ${pos.y + tileHeight} Z`;
|
||||
|
||||
g.append("path")
|
||||
.attr("d", path)
|
||||
.attr("fill", "#222") // Dark tile
|
||||
.attr("stroke", "none");
|
||||
return;
|
||||
}
|
||||
|
||||
const pos = project(d.gridX, d.gridY);
|
||||
const totalHeight = heightScale(d.github + d.gitea);
|
||||
const giteaH = heightScale(d.gitea);
|
||||
const githubH = heightScale(d.github);
|
||||
|
||||
// Draw Gitea Bar (Bottom)
|
||||
if (d.gitea > 0) {
|
||||
drawBar(g, pos.x, pos.y, tileWidth, tileHeight, giteaH, colorGitea, `Gitea: ${d.gitea} on ${d.date.toDateString()}`);
|
||||
}
|
||||
|
||||
// Draw GitHub Bar (Top)
|
||||
// Adjust y position up by gitea height
|
||||
if (d.github > 0) {
|
||||
drawBar(g, pos.x, pos.y - giteaH, tileWidth, tileHeight, githubH, colorGithub, `GitHub: ${d.github} on ${d.date.toDateString()}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Function to draw isometric prism
|
||||
function drawBar(container, x, y, w, h, z, color, tooltipText) {
|
||||
// Top Face
|
||||
const pathTop = `M${x} ${y - z}
|
||||
L${x + w} ${y + h - z}
|
||||
L${x} ${y + 2 * h - z}
|
||||
L${x - w} ${y + h - z} Z`;
|
||||
|
||||
// Right Face
|
||||
const pathRight = `M${x + w} ${y + h - z}
|
||||
L${x + w} ${y + h}
|
||||
L${x} ${y + 2 * h}
|
||||
L${x} ${y + 2 * h - z} Z`;
|
||||
|
||||
// Left Face
|
||||
const pathLeft = `M${x - w} ${y + h - z}
|
||||
L${x - w} ${y + h}
|
||||
L${x} ${y + 2 * h}
|
||||
L${x} ${y + 2 * h - z} Z`;
|
||||
|
||||
const group = container.append("g");
|
||||
|
||||
// Shading
|
||||
const c = d3.color(color);
|
||||
const cRight = c.darker(0.7);
|
||||
const cLeft = c.darker(0.4);
|
||||
|
||||
group.append("path").attr("d", pathRight).attr("fill", cRight);
|
||||
group.append("path").attr("d", pathLeft).attr("fill", cLeft);
|
||||
group.append("path").attr("d", pathTop).attr("fill", c);
|
||||
|
||||
// Simple tooltip title
|
||||
group.append("title").text(tooltipText);
|
||||
|
||||
// Hover effect
|
||||
// group.on("mouseenter", function() {
|
||||
// d3.select(this).selectAll("path").attr("opacity", 0.8);
|
||||
// }).on("mouseleave", function() {
|
||||
// d3.select(this).selectAll("path").attr("opacity", 1);
|
||||
// });
|
||||
}
|
||||
|
||||
}, [data, loading]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ height: '600px', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#0a0a0a', color: '#666' }}>
|
||||
ANALYZING COMMITS...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div style={{ height: '600px', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#0a0a0a', color: '#ff4d00' }}>
|
||||
DATA FLUX ERROR
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section style={{
|
||||
height: '600px',
|
||||
background: '#0a0a0a',
|
||||
color: '#e4e4e4',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '40px 20px',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<h2 className="uppercase" style={{ fontSize: '1.5rem', marginBottom: '10px', color: '#888' }}>
|
||||
Contribution Topography
|
||||
</h2>
|
||||
<div style={{ display: 'flex', gap: '20px', fontSize: '0.8rem', marginBottom: '20px', fontFamily: 'monospace' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
||||
<span style={{ width: '10px', height: '10px', background: '#2da44e' }}></span> GitHub
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
||||
<span style={{ width: '10px', height: '10px', background: '#ff4d00' }}></span> Gitea
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svg ref={svgRef} width="1000" height="600" style={{ maxWidth: '100%', height: 'auto', overflow: 'visible' }} />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActivityHeatmap;
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import WhereAmI from './WhereAmI';
|
||||
import GithubHistory from './GithubHistory';
|
||||
import ActivityHeatmap from './ActivityHeatmap';
|
||||
|
||||
const InfoTabs = () => {
|
||||
const [activeTab, setActiveTab] = useState('location');
|
||||
@@ -47,7 +48,24 @@ const InfoTabs = () => {
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
>
|
||||
Github History
|
||||
History
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('activity')}
|
||||
style={{
|
||||
background: 'transparent',
|
||||
border: 'none',
|
||||
borderBottom: activeTab === 'activity' ? '2px solid #ff4d00' : '2px solid transparent',
|
||||
color: activeTab === 'activity' ? '#fff' : '#666',
|
||||
padding: '10px 20px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '0.9rem',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em',
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
>
|
||||
Activity 3D
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -55,6 +73,7 @@ const InfoTabs = () => {
|
||||
<div>
|
||||
{activeTab === 'location' && <WhereAmI />}
|
||||
{activeTab === 'github' && <GithubHistory />}
|
||||
{activeTab === 'activity' && <ActivityHeatmap />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,4 +4,13 @@ import react from '@vitejs/plugin-react'
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
proxy: {
|
||||
'/api/gitea': {
|
||||
target: 'https://git.mati.ss',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api\/gitea/, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user