diff --git a/package-lock.json b/package-lock.json index c822d61..b0e5dba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,11 @@ "license": "MIT", "dependencies": { "@reduxjs/toolkit": "^1.7.1", - "@sentry/react": "^6.16.1", - "@sentry/tracing": "^6.16.1", + "@sentry/react": "^6.17.0", + "@sentry/tracing": "^6.17.0", "@speedy-tuner/ini": "^0.3.0", "@speedy-tuner/types": "^0.3.0", - "antd": "^4.18.4", + "antd": "^4.18.5", "firebase": "^9.6.4", "kbar": "^0.1.0-beta.27", "mlg-converter": "^0.5.1", @@ -3828,13 +3828,13 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" }, "node_modules/@sentry/browser": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.16.1.tgz", - "integrity": "sha512-F2I5RL7RTLQF9CccMrqt73GRdK3FdqaChED3RulGQX5lH6U3exHGFxwyZxSrY4x6FedfBFYlfXWWCJXpLnFkow==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.17.1.tgz", + "integrity": "sha512-Tj4uXNRA6dAwKzOwLGvTOsD8WNk6tiPaIYXPsIIOdBjDsFBfiu4JY8ygCzQpj7Fwy8qL6UULygU4kBuAx2ieyQ==", "dependencies": { - "@sentry/core": "6.16.1", - "@sentry/types": "6.16.1", - "@sentry/utils": "6.16.1", + "@sentry/core": "6.17.1", + "@sentry/types": "6.17.1", + "@sentry/utils": "6.17.1", "tslib": "^1.9.3" }, "engines": { @@ -3842,14 +3842,14 @@ } }, "node_modules/@sentry/core": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.16.1.tgz", - "integrity": "sha512-UFI0264CPUc5cR1zJH+S2UPOANpm6dLJOnsvnIGTjsrwzR0h8Hdl6rC2R/GPq+WNbnipo9hkiIwDlqbqvIU5vw==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.17.1.tgz", + "integrity": "sha512-dRcKs3+IKx2w7+xwg3mWRc4ghjnY+36W+qCOJXRbWyRjlzb67Es+TDYY3BxWwN4beNr2dvQkRt5HoPxJnrzGVQ==", "dependencies": { - "@sentry/hub": "6.16.1", - "@sentry/minimal": "6.16.1", - "@sentry/types": "6.16.1", - "@sentry/utils": "6.16.1", + "@sentry/hub": "6.17.1", + "@sentry/minimal": "6.17.1", + "@sentry/types": "6.17.1", + "@sentry/utils": "6.17.1", "tslib": "^1.9.3" }, "engines": { @@ -3857,12 +3857,12 @@ } }, "node_modules/@sentry/hub": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.16.1.tgz", - "integrity": "sha512-4PGtg6AfpqMkreTpL7ymDeQ/U1uXv03bKUuFdtsSTn/FRf9TLS4JB0KuTZCxfp1IRgAA+iFg6B784dDkT8R9eg==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.17.1.tgz", + "integrity": "sha512-14lNcM4kt2sKhsfZ6WG2gbsPMydxBAY1OSk+WodO/x0Esdr7VbaVngSJSFzCI0iRBPLSQuFVtUWaAZzSF+WdiA==", "dependencies": { - "@sentry/types": "6.16.1", - "@sentry/utils": "6.16.1", + "@sentry/types": "6.17.1", + "@sentry/utils": "6.17.1", "tslib": "^1.9.3" }, "engines": { @@ -3870,12 +3870,12 @@ } }, "node_modules/@sentry/minimal": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.16.1.tgz", - "integrity": "sha512-dq+mI1EQIvUM+zJtGCVgH3/B3Sbx4hKlGf2Usovm9KoqWYA+QpfVBholYDe/H2RXgO7LFEefDLvOdHDkqeJoyA==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.17.1.tgz", + "integrity": "sha512-0V5YqVCylMjjDhD98Xod1ZdxzVRUjDSq7ssCJ2PRjfRAEtk7mN5oeznyZUhyx4pZB8hNh2G9PdasIR2WIjYN9w==", "dependencies": { - "@sentry/hub": "6.16.1", - "@sentry/types": "6.16.1", + "@sentry/hub": "6.17.1", + "@sentry/types": "6.17.1", "tslib": "^1.9.3" }, "engines": { @@ -3883,14 +3883,14 @@ } }, "node_modules/@sentry/react": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-6.16.1.tgz", - "integrity": "sha512-n8fOEKbym4kBi946q3AWXBNy1UKTmABj/hE2nAJbTWhi5IwdM7WBG6QCT2yq7oTHLuTxQrAwgKQc+A6zFTyVHg==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-6.17.1.tgz", + "integrity": "sha512-Woq1QCtpXv4yXetOv68/V61vaUIVrcs7K0SZ3GZxdMJMmu3hpECFZIClFYE4z2fFT/ygueGJoGAL177R7/U3Vw==", "dependencies": { - "@sentry/browser": "6.16.1", - "@sentry/minimal": "6.16.1", - "@sentry/types": "6.16.1", - "@sentry/utils": "6.16.1", + "@sentry/browser": "6.17.1", + "@sentry/minimal": "6.17.1", + "@sentry/types": "6.17.1", + "@sentry/utils": "6.17.1", "hoist-non-react-statics": "^3.3.2", "tslib": "^1.9.3" }, @@ -3902,14 +3902,14 @@ } }, "node_modules/@sentry/tracing": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.16.1.tgz", - "integrity": "sha512-MPSbqXX59P+OEeST+U2V/8Hu/8QjpTUxTNeNyTHWIbbchdcMMjDbXTS3etCgajZR6Ro+DHElOz5cdSxH6IBGlA==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.17.1.tgz", + "integrity": "sha512-2to3Y3I+kdoJWdbPbK4llALXc+765W0SAAghFWEJ5L3mups59CGf03HtRHPE8p2Hw2Tr6YO6gjtZhvm+I49LPg==", "dependencies": { - "@sentry/hub": "6.16.1", - "@sentry/minimal": "6.16.1", - "@sentry/types": "6.16.1", - "@sentry/utils": "6.16.1", + "@sentry/hub": "6.17.1", + "@sentry/minimal": "6.17.1", + "@sentry/types": "6.17.1", + "@sentry/utils": "6.17.1", "tslib": "^1.9.3" }, "engines": { @@ -3917,19 +3917,19 @@ } }, "node_modules/@sentry/types": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.16.1.tgz", - "integrity": "sha512-Wh354g30UsJ5kYJbercektGX4ZMc9MHU++1NjeN2bTMnbofEcpUDWIiKeulZEY65IC1iU+1zRQQgtYO+/hgCUQ==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.17.1.tgz", + "integrity": "sha512-EBFiN3utd1xoxowy+WFSDQGgS4J+VEZbc7j3uYVZNjn03/w5pe9FfeLsszJ2s1/hCf/K7GAGH4NA7i0r7SWF3g==", "engines": { "node": ">=6" } }, "node_modules/@sentry/utils": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.16.1.tgz", - "integrity": "sha512-7ngq/i4R8JZitJo9Sl8PDnjSbDehOxgr1vsoMmerIsyRZ651C/8B+jVkMhaAPgSdyJ0AlE3O7DKKTP1FXFw9qw==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.17.1.tgz", + "integrity": "sha512-FOjxMZ4yBflYvJYhNDacRfSa0NTKLsjCXQ3u1phxODRUwhQ7wwqmtwy86AL5uzfpiqZKwXAPlWoWihBluw7vqw==", "dependencies": { - "@sentry/types": "6.16.1", + "@sentry/types": "6.17.1", "tslib": "^1.9.3" }, "engines": { @@ -5356,22 +5356,21 @@ } }, "node_modules/antd": { - "version": "4.18.4", - "resolved": "https://registry.npmjs.org/antd/-/antd-4.18.4.tgz", - "integrity": "sha512-7KCEhIyPeQJF/OenkfOTcx+5sHpiI5U6OzYmTUJn9wVPjcl07eFXu2w9teM9pJV9X7mSUWyPeM5aMFmBQo2TNQ==", + "version": "4.18.5", + "resolved": "https://registry.npmjs.org/antd/-/antd-4.18.5.tgz", + "integrity": "sha512-5fN3C2lWAzonhOYYlNpzIw2OHl7vxFZ+4cJ7DK/XZrV+75OY61Y+OkanqMJwrFtDDamIez35OM7cAezGko9tew==", "dependencies": { "@ant-design/colors": "^6.0.0", "@ant-design/icons": "^4.7.0", "@ant-design/react-slick": "~0.28.1", "@babel/runtime": "^7.12.5", "@ctrl/tinycolor": "^3.4.0", - "array-tree-filter": "^2.1.0", "classnames": "^2.2.6", "copy-to-clipboard": "^3.2.0", "lodash": "^4.17.21", "memoize-one": "^6.0.0", "moment": "^2.25.3", - "rc-cascader": "~3.0.0-alpha.8", + "rc-cascader": "~3.2.1", "rc-checkbox": "~2.3.0", "rc-collapse": "~3.1.0", "rc-dialog": "~8.6.0", @@ -5398,7 +5397,7 @@ "rc-textarea": "~0.3.0", "rc-tooltip": "~5.1.1", "rc-tree": "~5.4.3", - "rc-tree-select": "~5.1.0", + "rc-tree-select": "~5.1.1", "rc-trigger": "^5.2.10", "rc-upload": "~4.3.0", "rc-util": "^5.14.0", @@ -18329,14 +18328,14 @@ } }, "node_modules/rc-cascader": { - "version": "3.0.0-alpha.8", - "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.0.0-alpha.8.tgz", - "integrity": "sha512-zZ6tczHacUy622E7m5aruCcB3ii+J5bhusCPpyb64LP9KbcKcquchdgWeeyVY/7K8BrJXOTOJW1MDZ9nxsWBWw==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.2.1.tgz", + "integrity": "sha512-Raxam9tFzBL4TCgHoyVcf7+Q2KSFneUk3FZXi9w1tfxEihLlezSH0oCNMjHJN8hxWwwx9ZbI9UzWTfFImjXc0Q==", "dependencies": { "@babel/runtime": "^7.12.5", "array-tree-filter": "^2.1.0", "classnames": "^2.3.1", - "rc-select": "~14.0.0-alpha.8", + "rc-select": "~14.0.0-alpha.23", "rc-tree": "~5.4.3", "rc-util": "^5.6.1" }, @@ -28086,84 +28085,84 @@ } }, "@sentry/browser": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.16.1.tgz", - "integrity": "sha512-F2I5RL7RTLQF9CccMrqt73GRdK3FdqaChED3RulGQX5lH6U3exHGFxwyZxSrY4x6FedfBFYlfXWWCJXpLnFkow==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.17.1.tgz", + "integrity": "sha512-Tj4uXNRA6dAwKzOwLGvTOsD8WNk6tiPaIYXPsIIOdBjDsFBfiu4JY8ygCzQpj7Fwy8qL6UULygU4kBuAx2ieyQ==", "requires": { - "@sentry/core": "6.16.1", - "@sentry/types": "6.16.1", - "@sentry/utils": "6.16.1", + "@sentry/core": "6.17.1", + "@sentry/types": "6.17.1", + "@sentry/utils": "6.17.1", "tslib": "^1.9.3" } }, "@sentry/core": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.16.1.tgz", - "integrity": "sha512-UFI0264CPUc5cR1zJH+S2UPOANpm6dLJOnsvnIGTjsrwzR0h8Hdl6rC2R/GPq+WNbnipo9hkiIwDlqbqvIU5vw==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.17.1.tgz", + "integrity": "sha512-dRcKs3+IKx2w7+xwg3mWRc4ghjnY+36W+qCOJXRbWyRjlzb67Es+TDYY3BxWwN4beNr2dvQkRt5HoPxJnrzGVQ==", "requires": { - "@sentry/hub": "6.16.1", - "@sentry/minimal": "6.16.1", - "@sentry/types": "6.16.1", - "@sentry/utils": "6.16.1", + "@sentry/hub": "6.17.1", + "@sentry/minimal": "6.17.1", + "@sentry/types": "6.17.1", + "@sentry/utils": "6.17.1", "tslib": "^1.9.3" } }, "@sentry/hub": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.16.1.tgz", - "integrity": "sha512-4PGtg6AfpqMkreTpL7ymDeQ/U1uXv03bKUuFdtsSTn/FRf9TLS4JB0KuTZCxfp1IRgAA+iFg6B784dDkT8R9eg==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.17.1.tgz", + "integrity": "sha512-14lNcM4kt2sKhsfZ6WG2gbsPMydxBAY1OSk+WodO/x0Esdr7VbaVngSJSFzCI0iRBPLSQuFVtUWaAZzSF+WdiA==", "requires": { - "@sentry/types": "6.16.1", - "@sentry/utils": "6.16.1", + "@sentry/types": "6.17.1", + "@sentry/utils": "6.17.1", "tslib": "^1.9.3" } }, "@sentry/minimal": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.16.1.tgz", - "integrity": "sha512-dq+mI1EQIvUM+zJtGCVgH3/B3Sbx4hKlGf2Usovm9KoqWYA+QpfVBholYDe/H2RXgO7LFEefDLvOdHDkqeJoyA==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.17.1.tgz", + "integrity": "sha512-0V5YqVCylMjjDhD98Xod1ZdxzVRUjDSq7ssCJ2PRjfRAEtk7mN5oeznyZUhyx4pZB8hNh2G9PdasIR2WIjYN9w==", "requires": { - "@sentry/hub": "6.16.1", - "@sentry/types": "6.16.1", + "@sentry/hub": "6.17.1", + "@sentry/types": "6.17.1", "tslib": "^1.9.3" } }, "@sentry/react": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-6.16.1.tgz", - "integrity": "sha512-n8fOEKbym4kBi946q3AWXBNy1UKTmABj/hE2nAJbTWhi5IwdM7WBG6QCT2yq7oTHLuTxQrAwgKQc+A6zFTyVHg==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-6.17.1.tgz", + "integrity": "sha512-Woq1QCtpXv4yXetOv68/V61vaUIVrcs7K0SZ3GZxdMJMmu3hpECFZIClFYE4z2fFT/ygueGJoGAL177R7/U3Vw==", "requires": { - "@sentry/browser": "6.16.1", - "@sentry/minimal": "6.16.1", - "@sentry/types": "6.16.1", - "@sentry/utils": "6.16.1", + "@sentry/browser": "6.17.1", + "@sentry/minimal": "6.17.1", + "@sentry/types": "6.17.1", + "@sentry/utils": "6.17.1", "hoist-non-react-statics": "^3.3.2", "tslib": "^1.9.3" } }, "@sentry/tracing": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.16.1.tgz", - "integrity": "sha512-MPSbqXX59P+OEeST+U2V/8Hu/8QjpTUxTNeNyTHWIbbchdcMMjDbXTS3etCgajZR6Ro+DHElOz5cdSxH6IBGlA==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.17.1.tgz", + "integrity": "sha512-2to3Y3I+kdoJWdbPbK4llALXc+765W0SAAghFWEJ5L3mups59CGf03HtRHPE8p2Hw2Tr6YO6gjtZhvm+I49LPg==", "requires": { - "@sentry/hub": "6.16.1", - "@sentry/minimal": "6.16.1", - "@sentry/types": "6.16.1", - "@sentry/utils": "6.16.1", + "@sentry/hub": "6.17.1", + "@sentry/minimal": "6.17.1", + "@sentry/types": "6.17.1", + "@sentry/utils": "6.17.1", "tslib": "^1.9.3" } }, "@sentry/types": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.16.1.tgz", - "integrity": "sha512-Wh354g30UsJ5kYJbercektGX4ZMc9MHU++1NjeN2bTMnbofEcpUDWIiKeulZEY65IC1iU+1zRQQgtYO+/hgCUQ==" + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.17.1.tgz", + "integrity": "sha512-EBFiN3utd1xoxowy+WFSDQGgS4J+VEZbc7j3uYVZNjn03/w5pe9FfeLsszJ2s1/hCf/K7GAGH4NA7i0r7SWF3g==" }, "@sentry/utils": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.16.1.tgz", - "integrity": "sha512-7ngq/i4R8JZitJo9Sl8PDnjSbDehOxgr1vsoMmerIsyRZ651C/8B+jVkMhaAPgSdyJ0AlE3O7DKKTP1FXFw9qw==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.17.1.tgz", + "integrity": "sha512-FOjxMZ4yBflYvJYhNDacRfSa0NTKLsjCXQ3u1phxODRUwhQ7wwqmtwy86AL5uzfpiqZKwXAPlWoWihBluw7vqw==", "requires": { - "@sentry/types": "6.16.1", + "@sentry/types": "6.17.1", "tslib": "^1.9.3" } }, @@ -29224,22 +29223,21 @@ } }, "antd": { - "version": "4.18.4", - "resolved": "https://registry.npmjs.org/antd/-/antd-4.18.4.tgz", - "integrity": "sha512-7KCEhIyPeQJF/OenkfOTcx+5sHpiI5U6OzYmTUJn9wVPjcl07eFXu2w9teM9pJV9X7mSUWyPeM5aMFmBQo2TNQ==", + "version": "4.18.5", + "resolved": "https://registry.npmjs.org/antd/-/antd-4.18.5.tgz", + "integrity": "sha512-5fN3C2lWAzonhOYYlNpzIw2OHl7vxFZ+4cJ7DK/XZrV+75OY61Y+OkanqMJwrFtDDamIez35OM7cAezGko9tew==", "requires": { "@ant-design/colors": "^6.0.0", "@ant-design/icons": "^4.7.0", "@ant-design/react-slick": "~0.28.1", "@babel/runtime": "^7.12.5", "@ctrl/tinycolor": "^3.4.0", - "array-tree-filter": "^2.1.0", "classnames": "^2.2.6", "copy-to-clipboard": "^3.2.0", "lodash": "^4.17.21", "memoize-one": "^6.0.0", "moment": "^2.25.3", - "rc-cascader": "~3.0.0-alpha.8", + "rc-cascader": "~3.2.1", "rc-checkbox": "~2.3.0", "rc-collapse": "~3.1.0", "rc-dialog": "~8.6.0", @@ -29266,7 +29264,7 @@ "rc-textarea": "~0.3.0", "rc-tooltip": "~5.1.1", "rc-tree": "~5.4.3", - "rc-tree-select": "~5.1.0", + "rc-tree-select": "~5.1.1", "rc-trigger": "^5.2.10", "rc-upload": "~4.3.0", "rc-util": "^5.14.0", @@ -39159,14 +39157,14 @@ } }, "rc-cascader": { - "version": "3.0.0-alpha.8", - "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.0.0-alpha.8.tgz", - "integrity": "sha512-zZ6tczHacUy622E7m5aruCcB3ii+J5bhusCPpyb64LP9KbcKcquchdgWeeyVY/7K8BrJXOTOJW1MDZ9nxsWBWw==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.2.1.tgz", + "integrity": "sha512-Raxam9tFzBL4TCgHoyVcf7+Q2KSFneUk3FZXi9w1tfxEihLlezSH0oCNMjHJN8hxWwwx9ZbI9UzWTfFImjXc0Q==", "requires": { "@babel/runtime": "^7.12.5", "array-tree-filter": "^2.1.0", "classnames": "^2.3.1", - "rc-select": "~14.0.0-alpha.8", + "rc-select": "~14.0.0-alpha.23", "rc-tree": "~5.4.3", "rc-util": "^5.6.1" } diff --git a/package.json b/package.json index fc0e1e0..33b40d8 100644 --- a/package.json +++ b/package.json @@ -32,11 +32,11 @@ }, "dependencies": { "@reduxjs/toolkit": "^1.7.1", - "@sentry/react": "^6.16.1", - "@sentry/tracing": "^6.16.1", + "@sentry/react": "^6.17.0", + "@sentry/tracing": "^6.17.0", "@speedy-tuner/ini": "^0.3.0", "@speedy-tuner/types": "^0.3.0", - "antd": "^4.18.4", + "antd": "^4.18.5", "firebase": "^9.6.4", "kbar": "^0.1.0-beta.27", "mlg-converter": "^0.5.1", diff --git a/src/App.tsx b/src/App.tsx index 6f562e7..b47c70d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -33,6 +33,7 @@ import { import useDb from './hooks/useDb'; import useServerStorage from './hooks/useServerStorage'; import Info from './pages/Info'; +import Hub from './pages/Hub'; import 'react-perfect-scrollbar/dist/css/styles.css'; @@ -127,11 +128,7 @@ const App = ({ ui, navigation }: { ui: UIState, navigation: NavigationState }) = - + diff --git a/src/components/CommandPalette.tsx b/src/components/CommandPalette.tsx index 4ca838f..bf55e39 100644 --- a/src/components/CommandPalette.tsx +++ b/src/components/CommandPalette.tsx @@ -30,6 +30,7 @@ import { InfoCircleOutlined, FundOutlined, SettingOutlined, + CarOutlined, } from '@ant-design/icons'; import { useHistory, @@ -313,6 +314,14 @@ const CommandPalette = (props: CommandPaletteProps) => { }, [logout]); const initialActions = [ + { + id: 'HubAction', + section: Sections.NAVIGATION, + name: 'Hub', + subtitle: 'Public tunes and logs.', + icon: , + perform: () => history.push(Routes.ROOT), + }, { id: 'ToggleSidebar', name: 'Toggle Sidebar', diff --git a/src/components/TopBar.tsx b/src/components/TopBar.tsx index 5517b5a..4a6de46 100644 --- a/src/components/TopBar.tsx +++ b/src/components/TopBar.tsx @@ -45,6 +45,7 @@ import { UserAddOutlined, LogoutOutlined, InfoCircleOutlined, + CarOutlined, } from '@ant-design/icons'; import { useKBar } from 'kbar'; import store from '../store'; @@ -74,6 +75,9 @@ const TopBar = ({ tuneId }: { tuneId: string | null }) => { const history = useHistory(); const { query } = useKBar(); const buildTuneUrl = (route: string) => tuneId ? generatePath(route, { tuneId }) : null; + const matchedRootPath = useMemo(() => matchPath(pathname, { + path: Routes.ROOT, + }), [pathname]); const matchedTuneRootPath = useMemo(() => matchPath(pathname, { path: Routes.TUNE_ROOT, }), [pathname]); @@ -114,33 +118,39 @@ const TopBar = ({ tuneId }: { tuneId: string | null }) => { history.push(e.target.value)} > + + + + {lg && 'Hub'} + + - {sm && 'Info'} + {lg && 'Info'} - {sm && 'Tune'} + {lg && 'Tune'} - {sm && 'Logs'} + {lg && 'Logs'} - {sm && 'Diagnose'} + {lg && 'Diagnose'} diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index f702ca6..6b33638 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -1,6 +1,14 @@ import { User, UserCredential, + createUserWithEmailAndPassword, + signInWithEmailAndPassword, + sendEmailVerification, + signOut, + sendPasswordResetEmail, + GoogleAuthProvider, + GithubAuthProvider, + signInWithPopup, } from 'firebase/auth'; import { createContext, @@ -10,17 +18,7 @@ import { useMemo, useState, } from 'react'; -import { - auth, - createUserWithEmailAndPassword, - signInWithEmailAndPassword, - sendEmailVerification, - signOut, - sendPasswordResetEmail, - GoogleAuthProvider, - GithubAuthProvider, - signInWithPopup, -} from '../firebase'; +import { auth } from '../firebase'; interface AuthValue { currentUser: User | null, diff --git a/src/firebase.ts b/src/firebase.ts index b362692..8ef50cc 100644 --- a/src/firebase.ts +++ b/src/firebase.ts @@ -1,33 +1,9 @@ import { initializeApp } from 'firebase/app'; import { getPerformance } from 'firebase/performance'; -import { - getAuth, - createUserWithEmailAndPassword, - signInWithEmailAndPassword, - signOut, - sendEmailVerification, - sendPasswordResetEmail, - GoogleAuthProvider, - GithubAuthProvider, - signInWithPopup, -} from 'firebase/auth'; +import { getAuth } from 'firebase/auth'; import { getAnalytics } from 'firebase/analytics'; -import { - getStorage, - ref, - uploadBytes, - uploadBytesResumable, - deleteObject, - getBytes, -} from 'firebase/storage'; -import { - getFirestore, - doc, - setDoc, - collection, - addDoc, - getDoc, -} from 'firebase/firestore'; +import { getStorage } from 'firebase/storage'; +import { getFirestore } from 'firebase/firestore'; const firebaseConfig = { apiKey: process.env.REACT_APP_FIREBASE_API_KEY, @@ -47,27 +23,10 @@ const db = getFirestore(); const storage = getStorage(); export { - auth, + app, analytics, performance, - createUserWithEmailAndPassword, - signInWithEmailAndPassword, - sendEmailVerification, - signOut, - sendPasswordResetEmail, - GoogleAuthProvider, - GithubAuthProvider, - signInWithPopup, - ref as storageRef, - storage, - uploadBytes, - uploadBytesResumable, - deleteObject, - getBytes, - doc as fireStoreDoc, - collection as fireStoreCollection, - setDoc, - addDoc, - getDoc, + auth, db, + storage, }; diff --git a/src/hooks/useDb.ts b/src/hooks/useDb.ts index b0820f4..49b63bc 100644 --- a/src/hooks/useDb.ts +++ b/src/hooks/useDb.ts @@ -1,12 +1,18 @@ import { notification } from 'antd'; import * as Sentry from '@sentry/browser'; -import { Timestamp } from 'firebase/firestore'; import { - fireStoreDoc, + Timestamp, + doc, getDoc, setDoc, - db, -} from '../firebase'; + collection, + where, + query, + getDocs, + QuerySnapshot, + orderBy, +} from 'firebase/firestore'; +import { db } from '../firebase'; import { TuneDbData } from '../types/dbData'; const TUNES_PATH = 'publicTunes'; @@ -14,9 +20,9 @@ const TUNES_PATH = 'publicTunes'; const genericError = (error: Error) => notification.error({ message: 'Database Error', description: error.message }); const useDb = () => { - const getData = async (tuneId: string) => { + const getTuneData = async (tuneId: string) => { try { - const tune = (await getDoc(fireStoreDoc(db, TUNES_PATH, tuneId))).data() as TuneDbData; + const tune = (await getDoc(doc(db, TUNES_PATH, tuneId))).data() as TuneDbData; const processed = { ...tune, createdAt: (tune?.createdAt as Timestamp)?.toDate().toISOString(), @@ -33,9 +39,29 @@ const useDb = () => { } }; + const listTunesData = async () => { + try { + const tunesRef = collection(db, TUNES_PATH); + const q = query( + tunesRef, + where('isPublished', '==', true), + where('isListed', '==', true), + orderBy('createdAt', 'desc'), + ); + + return Promise.resolve(await getDocs(q)); + } catch (error) { + Sentry.captureException(error); + console.error(error); + genericError(error as Error); + + return Promise.reject(error); + } + }; + const updateData = async (tuneId: string, data: TuneDbData) => { try { - await setDoc(fireStoreDoc(db, TUNES_PATH, tuneId), data, { merge: true }); + await setDoc(doc(db, TUNES_PATH, tuneId), data, { merge: true }); return Promise.resolve(); } catch (error) { @@ -48,8 +74,9 @@ const useDb = () => { }; return { - getTune: (tuneId: string): Promise => getData(tuneId), updateData: (tuneId: string, data: TuneDbData): Promise => updateData(tuneId, data), + getTune: (tuneId: string): Promise => getTuneData(tuneId), + listTunes: (): Promise> => listTunesData(), }; }; diff --git a/src/hooks/useServerStorage.ts b/src/hooks/useServerStorage.ts index f7004f9..6ba3fa5 100644 --- a/src/hooks/useServerStorage.ts +++ b/src/hooks/useServerStorage.ts @@ -1,13 +1,13 @@ import { notification } from 'antd'; import * as Sentry from '@sentry/browser'; -import { UploadTask } from 'firebase/storage'; import { - storage, - storageRef, + UploadTask, + ref, getBytes, deleteObject, uploadBytesResumable, -} from '../firebase'; +} from 'firebase/storage'; +import { storage } from '../firebase'; const BASE_PATH = 'public/users'; @@ -16,7 +16,7 @@ const genericError = (error: Error) => notification.error({ message: 'Database E const useServerStorage = () => { const getFile = async (path: string) => { try { - const buffer = await getBytes(storageRef(storage, path)); + const buffer = await getBytes(ref(storage, path)); return Promise.resolve(buffer); } catch (error) { @@ -30,7 +30,7 @@ const useServerStorage = () => { const removeFile = async (path: string) => { try { - await deleteObject(storageRef(storage, path)); + await deleteObject(ref(storage, path)); return Promise.resolve(); } catch (error) { @@ -43,7 +43,7 @@ const useServerStorage = () => { }; const uploadFile = (path: string, file: File, data: Uint8Array) => - uploadBytesResumable(storageRef(storage, path), data, { + uploadBytesResumable(ref(storage, path), data, { customMetadata: { name: file.name, size: `${file.size}`, diff --git a/src/pages/Hub.tsx b/src/pages/Hub.tsx new file mode 100644 index 0000000..31b52e3 --- /dev/null +++ b/src/pages/Hub.tsx @@ -0,0 +1,111 @@ +import { + Badge, + Card, + Col, + Row, + Tooltip, + Typography, +} from 'antd'; +import { + CopyOutlined, + StarOutlined, + ArrowRightOutlined, +} from '@ant-design/icons'; +import { + useCallback, + useEffect, + useState, +} from 'react'; +import { + generatePath, + useHistory, +} from 'react-router'; +import useDb from '../hooks/useDb'; +import { TuneDbData } from '../types/dbData'; +import { Routes } from '../routes'; +import { generateShareUrl } from '../utils/url'; + +const containerStyle = { + padding: 20, + maxWidth: 800, + margin: '0 auto', +}; + +const loadingCards = ( + <> + + + + + + + + + + +); + +const Hub = () => { + const [tunes, setTunes] = useState([]); + const { listTunes } = useDb(); + const history = useHistory(); + const [copied, setCopied] = useState(false); + + const goToTune = (tuneId: string) => history.push(generatePath(Routes.TUNE_ROOT, { tuneId })); + + const copyToClipboard = async (shareUrl: string) => { + if (navigator.clipboard) { + await navigator.clipboard.writeText(shareUrl); + setCopied(true); + setTimeout(() => setCopied(false), 1000); + } + }; + + const loadData = useCallback(() => { + listTunes().then((data) => { + const temp: TuneDbData[] = []; + + data.forEach((tuneSnapshot) => { + temp.push(tuneSnapshot.data()); + }); + + setTunes(temp); + }); + }, [listTunes]); + + useEffect(() => { + loadData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); // TODO: fix this + + return ( +
+ Hub + + {tunes.length === 0 ? loadingCards : ( + tunes.map((tune) => ( + + + + , + + copyToClipboard(generateShareUrl(tune.id!))} /> + , + goToTune(tune.id!)} />, + ]} + > + + {tune.details!.make} {tune.details!.model} {tune.details!.year} + + + + )))} + +
+ ); +}; + +export default Hub; diff --git a/src/pages/Upload.tsx b/src/pages/Upload.tsx index 9b2a7a5..22ea1b3 100644 --- a/src/pages/Upload.tsx +++ b/src/pages/Upload.tsx @@ -53,9 +53,9 @@ import { Routes } from '../routes'; import TuneParser from '../utils/tune/TuneParser'; import TriggerLogsParser from '../utils/logs/TriggerLogsParser'; import LogParser from '../utils/logs/LogParser'; -import { TuneDbData } from '../types/dbData'; import useDb from '../hooks/useDb'; import useServerStorage from '../hooks/useServerStorage'; +import { generateShareUrl } from '../utils/url'; const { Item } = Form; @@ -116,7 +116,7 @@ const UploadPage = () => { const { currentUser, refreshToken } = useAuth(); const history = useHistory(); const { removeFile, uploadFile, basePathForFile } = useServerStorage(); - const { updateData, getTune } = useDb(); + const { updateData } = useDb(); const requiredRules = [{ required: true, message: 'This field is required!' }]; const [readme, setReadme] = useState('# My Tune\n\ndescription'); @@ -139,7 +139,8 @@ const UploadPage = () => { const publish = async (values: any) => { setIsLoading(true); await updateData(newTuneId!, { - createdAt: new Date(), + id: newTuneId!, + userUid: currentUser!.uid, updatedAt: new Date(), isPublished: true, isListed: values.isListed, @@ -225,29 +226,25 @@ const UploadPage = () => { }); const uploadTune = async (options: UploadRequestOption) => { - const found = await getTune(newTuneId!); - if (!found) { - const tuneData: TuneDbData = { + setShareUrl(generateShareUrl(newTuneId!)); + + const { path } = (options.data as unknown as UploadFileData); + const tune: UploadedFile = {}; + tune[(options.file as UploadFile).uid] = path; + + upload(path, options, () => { + // this is `create` for firebase + // initialize data + updateData(newTuneId!, { + id: newTuneId!, userUid: currentUser!.uid, createdAt: new Date(), updatedAt: new Date(), isPublished: false, isListed: true, - tuneFile: null, - logFiles: [], - toothLogFiles: [], - customIniFile: null, details: {}, - }; - await updateData(newTuneId!, tuneData); - } - setShareUrl(`${process.env.REACT_APP_WEB_URL}/#/t/${newTuneId}`); - - const { path } = (options.data as unknown as UploadFileData); - const tune: UploadedFile = {}; - tune[(options.file as UploadFile).uid] = path; - upload(path, options, () => { - updateData(newTuneId!, { tuneFile: path }); + tuneFile: path, + }); }, async (file) => { const { result, message } = await validateSize(file); if (!result) { @@ -432,7 +429,9 @@ const UploadPage = () => { genericError(error as Error); } - setNewTuneId(nanoidCustom()); + const tuneId = nanoidCustom(); + setNewTuneId(tuneId); + console.log('New tuneId:', tuneId); }, [currentUser, history, refreshToken]); useEffect(() => { diff --git a/src/types/dbData.ts b/src/types/dbData.ts index 03749a3..e03c5a0 100644 --- a/src/types/dbData.ts +++ b/src/types/dbData.ts @@ -17,6 +17,7 @@ export interface TuneDataDetails { } export interface TuneDbData { + id?: string, userUid?: string; createdAt?: Date | Timestamp | string; updatedAt?: Date | Timestamp | string; diff --git a/src/utils/url.ts b/src/utils/url.ts new file mode 100644 index 0000000..eb610b9 --- /dev/null +++ b/src/utils/url.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export const generateShareUrl = (tuneId: string) => `${process.env.REACT_APP_WEB_URL}/#/t/${tuneId}`;