diff --git a/package.json b/package.json index 58a5cf8..e6d1e27 100644 --- a/package.json +++ b/package.json @@ -23,12 +23,14 @@ "@trigger.dev/nextjs": "^3.0.8", "@trigger.dev/react": "^3.0.8", "@trigger.dev/sdk": "^3.0.8", + "canvas": "3.0.0-rc2", "chart.js": "^4.4.4", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "comlink": "^4.4.1", "dexie": "^4.0.8", "dexie-react-hooks": "^1.1.7", + "extract-colors": "^4.0.8", "framer-motion": "^11.5.4", "js-cookie": "^3.0.5", "ky": "^1.7.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b08050c..d50e2f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: '@trigger.dev/sdk': specifier: ^3.0.8 version: 3.0.8 + canvas: + specifier: 3.0.0-rc2 + version: 3.0.0-rc2 chart.js: specifier: ^4.4.4 version: 4.4.4 @@ -68,6 +71,9 @@ importers: dexie-react-hooks: specifier: ^1.1.7 version: 1.1.7(@types/react@18.3.10)(dexie@4.0.8)(react@19.0.0-rc-3edc000d-20240926) + extract-colors: + specifier: ^4.0.8 + version: 4.0.8 framer-motion: specifier: ^11.5.4 version: 11.9.0(react-dom@19.0.0-rc-3edc000d-20240926(react@19.0.0-rc-3edc000d-20240926))(react@19.0.0-rc-3edc000d-20240926) @@ -1134,10 +1140,16 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1152,6 +1164,9 @@ packages: resolution: {integrity: sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==} engines: {node: '>=16.20.1'} + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -1171,6 +1186,10 @@ packages: caniuse-lite@1.0.30001657: resolution: {integrity: sha512-DPbJAlP8/BAXy3IgiWmZKItubb3TYGP0WscQQlVGIfT4s/YlFYVuJgyOsQNP7rJRChx/qdMeLJQJP0Sgg2yjNA==} + canvas@3.0.0-rc2: + resolution: {integrity: sha512-esx4bYDznnqgRX4G8kaEaf0W3q8xIc51WpmrIitDzmcoEgwnv9wSKdzT6UxWZ4wkVu5+ileofppX0TpyviJRdQ==} + engines: {node: ^18.12.0 || >= 20.9.0} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1187,6 +1206,9 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + cjs-module-lexer@1.4.1: resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} @@ -1284,10 +1306,22 @@ packages: supports-color: optional: true + decompress-response@4.2.1: + resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==} + engines: {node: '>=8'} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + deep-equal@2.2.3: resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} engines: {node: '>= 0.4'} + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1340,6 +1374,9 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + engine.io-client@6.5.4: resolution: {integrity: sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==} @@ -1507,6 +1544,13 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + extract-colors@4.0.8: + resolution: {integrity: sha512-XW6Ee9bgdx4Z9GchZm2likadze+BjQG1orkSyfxjd9znvxxtc7UBHf//1lFMtQm+lDusO3Fz8giaok0LWyflOA==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1563,6 +1607,9 @@ packages: react-dom: optional: true + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -1600,6 +1647,9 @@ packages: get-tsconfig@4.8.0: resolution: {integrity: sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==} + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1675,6 +1725,9 @@ packages: humanize-duration@3.32.1: resolution: {integrity: sha512-inh5wue5XdfObhu/IGEMiA1nUXigSGcaKNemcbLRKa7jXYGDZXr3LoT9pTIzq2hPEbld7w/qv9h+ikWGz8fL1g==} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1697,6 +1750,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + internal-slot@1.0.7: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} @@ -1952,6 +2008,14 @@ packages: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} + mimic-response@2.1.0: + resolution: {integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==} + engines: {node: '>=8'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + minimal-polyfills@2.2.3: resolution: {integrity: sha512-oxdmJ9cL+xV72h0xYxp4tP2d5/fTBpP45H8DIOn9pASuF8a3IYTf+25fMGDYGiWW+MFsuog6KD6nfmhZJQ+uUw==} @@ -1973,6 +2037,9 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + module-details-from-path@1.0.3: resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} @@ -2029,6 +2096,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -2063,6 +2133,13 @@ packages: sass: optional: true + node-abi@3.68.0: + resolution: {integrity: sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A==} + engines: {node: '>=10'} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -2228,6 +2305,11 @@ packages: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} + prebuild-install@7.1.2: + resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} + engines: {node: '>=10'} + hasBin: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -2239,6 +2321,9 @@ packages: resolution: {integrity: sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==} engines: {node: '>=12.0.0'} + pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2246,6 +2331,10 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + react-chartjs-2@5.2.0: resolution: {integrity: sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==} peerDependencies: @@ -2273,6 +2362,10 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -2327,6 +2420,9 @@ packages: resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} engines: {node: '>=0.4'} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-regex-test@1.0.3: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} @@ -2377,6 +2473,15 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@3.1.1: + resolution: {integrity: sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} @@ -2439,6 +2544,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -2455,6 +2563,10 @@ packages: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -2510,6 +2622,13 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + terminal-link@3.0.0: resolution: {integrity: sha512-flFL3m4wuixmf6IfhFJd1YPiLiMuxEc8uHRM1buzIeZPm22Au2pDqBJQgdo7n1WfPU1ONFGv7YDwpFBmHGF6lg==} engines: {node: '>=12'} @@ -2550,6 +2669,9 @@ packages: tslib@2.7.0: resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -3732,8 +3854,16 @@ snapshots: balanced-match@1.0.2: {} + base64-js@1.5.1: {} + binary-extensions@2.3.0: {} + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -3749,6 +3879,11 @@ snapshots: bson@6.8.0: {} + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -3767,6 +3902,12 @@ snapshots: caniuse-lite@1.0.30001657: {} + canvas@3.0.0-rc2: + dependencies: + node-addon-api: 7.1.1 + prebuild-install: 7.1.2 + simple-get: 3.1.1 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -3790,6 +3931,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chownr@1.1.4: {} + cjs-module-lexer@1.4.1: {} class-variance-authority@0.7.0: @@ -3876,6 +4019,14 @@ snapshots: dependencies: ms: 2.1.3 + decompress-response@4.2.1: + dependencies: + mimic-response: 2.1.0 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + deep-equal@2.2.3: dependencies: array-buffer-byte-length: 1.0.1 @@ -3897,6 +4048,8 @@ snapshots: which-collection: 1.0.2 which-typed-array: 1.1.15 + deep-extend@0.6.0: {} + deep-is@0.1.4: {} define-data-property@1.1.4: @@ -3911,8 +4064,7 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - detect-libc@2.0.3: - optional: true + detect-libc@2.0.3: {} dexie-react-hooks@1.1.7(@types/react@18.3.10)(dexie@4.0.8)(react@19.0.0-rc-3edc000d-20240926): dependencies: @@ -3944,6 +4096,10 @@ snapshots: emoji-regex@9.2.2: {} + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + engine.io-client@6.5.4: dependencies: '@socket.io/component-emitter': 3.1.2 @@ -4289,6 +4445,10 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 + expand-template@2.0.3: {} + + extract-colors@4.0.8: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.2: @@ -4344,6 +4504,8 @@ snapshots: react: 19.0.0-rc-3edc000d-20240926 react-dom: 19.0.0-rc-3edc000d-20240926(react@19.0.0-rc-3edc000d-20240926) + fs-constants@1.0.0: {} + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -4382,6 +4544,8 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + github-from-package@0.0.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -4466,6 +4630,8 @@ snapshots: humanize-duration@3.32.1: {} + ieee754@1.2.1: {} + ignore@5.3.2: {} import-fresh@3.3.0: @@ -4489,6 +4655,8 @@ snapshots: inherits@2.0.4: {} + ini@1.3.8: {} + internal-slot@1.0.7: dependencies: es-errors: 1.3.0 @@ -4718,6 +4886,10 @@ snapshots: mimic-fn@4.0.0: {} + mimic-response@2.1.0: {} + + mimic-response@3.1.0: {} + minimal-polyfills@2.2.3: {} minimatch@3.1.2: @@ -4736,6 +4908,8 @@ snapshots: minipass@7.1.2: {} + mkdirp-classic@0.5.3: {} + module-details-from-path@1.0.3: {} mongodb-connection-string-url@3.0.1: @@ -4786,6 +4960,8 @@ snapshots: nanoid@3.3.7: {} + napi-build-utils@1.0.2: {} + natural-compare@1.4.0: {} next-build-id@3.0.0: {} @@ -4822,6 +4998,12 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-abi@3.68.0: + dependencies: + semver: 7.6.3 + + node-addon-api@7.1.1: {} + normalize-path@3.0.0: {} npm-run-path@5.3.0: @@ -4974,6 +5156,21 @@ snapshots: picocolors: 1.1.0 source-map-js: 1.2.1 + prebuild-install@7.1.2: + dependencies: + detect-libc: 2.0.3 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.68.0 + pump: 3.0.2 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 + prelude-ls@1.2.1: {} prop-types@15.8.1: @@ -4997,10 +5194,22 @@ snapshots: '@types/node': 20.16.10 long: 5.2.3 + pump@3.0.2: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + punycode@2.3.1: {} queue-microtask@1.2.3: {} + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + react-chartjs-2@5.2.0(chart.js@4.4.4)(react@19.0.0-rc-3edc000d-20240926): dependencies: chart.js: 4.4.4 @@ -5023,6 +5232,12 @@ snapshots: dependencies: pify: 2.3.0 + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -5091,6 +5306,8 @@ snapshots: has-symbols: 1.0.3 isarray: 2.0.5 + safe-buffer@5.2.1: {} + safe-regex-test@1.0.3: dependencies: call-bind: 1.0.7 @@ -5165,6 +5382,20 @@ snapshots: signal-exit@4.1.0: {} + simple-concat@1.0.1: {} + + simple-get@3.1.1: + dependencies: + decompress-response: 4.2.1 + once: 1.4.0 + simple-concat: 1.0.1 + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 @@ -5260,6 +5491,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -5272,6 +5507,8 @@ snapshots: strip-final-newline@3.0.0: {} + strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} styled-jsx@5.1.3(react@19.0.0-rc-3edc000d-20240926): @@ -5339,6 +5576,21 @@ snapshots: tapable@2.2.1: {} + tar-fs@2.1.1: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.2 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + terminal-link@3.0.0: dependencies: ansi-escapes: 5.0.0 @@ -5379,6 +5631,10 @@ snapshots: tslib@2.7.0: {} + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/src/app/(pages)/player/[...slug]/page.tsx b/src/app/(pages)/player/[...slug]/page.tsx index 3532f21..e563388 100644 --- a/src/app/(pages)/player/[...slug]/page.tsx +++ b/src/app/(pages)/player/[...slug]/page.tsx @@ -3,8 +3,16 @@ import { scoresaberService } from "@/common/service/impl/scoresaber"; import { ScoreSort } from "@/common/service/score-sort"; import PlayerData from "@/components/player/player-data"; import { format } from "@formkit/tempo"; -import { Metadata } from "next"; +import { Metadata, Viewport } from "next"; import { redirect } from "next/navigation"; +import { Colors } from "@/common/colors"; +import ScoreSaberPlayerScoresPageToken from "@/common/model/token/scoresaber/score-saber-player-scores-page-token"; +import { getAverageColor } from "@/common/image-utils"; + +const UNKNOWN_PLAYER = { + title: "ScoreSaber Reloaded - Unknown Player", + description: "The player you were looking for could not be found.", +}; type Props = { params: Promise<{ @@ -15,19 +23,52 @@ type Props = { }>; }; -export async function generateMetadata({ params }: Props): Promise { +/** + * Gets the player data and scores + * + * @param params the params + * @param fetchScores whether to fetch the scores + * @returns the player data and scores + */ +async function getPlayerData({ params }: Props, fetchScores: boolean = true) { const { slug } = await params; const id = slug[0]; // The players id - const response = await scoresaberService.lookupPlayer(id, false); - if (response === undefined) { + const sort: ScoreSort = (slug[1] as ScoreSort) || "recent"; // The sorting method + const page = parseInt(slug[2]) || 1; // The page number + const search = (slug[3] as string) || ""; // The search query + + const player = (await scoresaberService.lookupPlayer(id, false))?.player; + let scores: ScoreSaberPlayerScoresPageToken | undefined; + if (fetchScores) { + scores = await scoresaberService.lookupPlayerScores({ + playerId: id, + sort, + page, + search, + }); + } + + return { + sort: sort, + page: page, + search: search, + player: player, + scores: scores, + }; +} + +export async function generateMetadata(props: Props): Promise { + const { player } = await getPlayerData(props, false); + if (player === undefined) { return { - title: `Unknown Player`, + title: UNKNOWN_PLAYER.title, + description: UNKNOWN_PLAYER.description, openGraph: { - title: `Unknown Player`, + title: UNKNOWN_PLAYER.title, + description: UNKNOWN_PLAYER.description, }, }; } - const { player } = response; return { title: `${player.name}`, @@ -43,27 +84,32 @@ export async function generateMetadata({ params }: Props): Promise { }; } -export default async function Search({ params, searchParams }: Props) { - const { slug } = await params; - const searchParamss = await searchParams; - const id = slug[0]; // The players id - const sort: ScoreSort = (slug[1] as ScoreSort) || "recent"; // The sorting method - const page = parseInt(slug[2]) || 1; // The page number - const search = searchParamss["search"] || ""; // The search query +export async function generateViewport(props: Props): Promise { + const { player } = await getPlayerData(props, false); + if (player === undefined) { + return { + themeColor: Colors.primary, + }; + } - const response = await scoresaberService.lookupPlayer(id, false); - if (response == undefined) { - // Invalid player id + const color = await getAverageColor(player.avatar); + if (color === undefined) { + return { + themeColor: Colors.primary, + }; + } + + return { + themeColor: color?.hex, + }; +} + +export default async function Search(props: Props) { + const { player, scores, sort, page, search } = await getPlayerData(props); + if (player == undefined) { return redirect("/"); } - const scores = await scoresaberService.lookupPlayerScores({ - playerId: id, - sort, - page, - search, - }); - const { player } = response; return (
{ + const before = performance.now(); + console.log(`Getting average color of "${src}"...`); + try { + const response = await ky.get(src); + if (response.status !== 200) { + return undefined; + } + + const imageBuffer = await response.arrayBuffer(); + + // Create an image from the buffer using canvas + const img = await loadImage(Buffer.from(imageBuffer)); + const canvas = createCanvas(img.width, img.height); + const ctx = canvas.getContext("2d"); + + // Draw the image onto the canvas + ctx.drawImage(img, 0, 0); + + // Get the pixel data from the canvas + const imageData = ctx.getImageData(0, 0, img.width, img.height); + const { data, width, height } = imageData; + + // Use your extractColors function to calculate the average color + const color = await extractColors({ data, width, height }); + + console.log( + `Found average color of "${src}" in ${(performance.now() - before).toFixed(0)}ms`, + ); + return color[2]; + } catch (error) { + console.error("Error while getting average color:", error); + return undefined; + } +}); diff --git a/src/common/model/player/impl/scoresaber-player.ts b/src/common/model/player/impl/scoresaber-player.ts index 2b6390d..4a268d6 100644 --- a/src/common/model/player/impl/scoresaber-player.ts +++ b/src/common/model/player/impl/scoresaber-player.ts @@ -121,8 +121,9 @@ export async function getScoreSaberPlayerFromToken( } // Sort the fallback history statisticHistory = Object.entries(statisticHistory) - .sort() + .sort((a, b) => Date.parse(b[0]) - Date.parse(a[0])) .reduce((obj, [key, value]) => ({ ...obj, [key]: value }), {}); + const yesterdayDate = formatDateMinimal( getMidnightAlignedDate(getDaysAgoDate(1)), );