diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 6f002535..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "node-irc"] - path = node-irc - url = https://github.com/yadPe/node-irc.git diff --git a/README.md b/README.md index 12ec0621..d4c90995 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,39 @@ # Beatconnect client +The official client for [Beatconnect](https://beatconnect.io) which is a mirror for [Osu!](https://osu.ppy.sh/home) Beatmaps -This App gives you access to all the beatmaps mirrored on [Beatconnect](https://beatconnect.io). You can downloads multiple beatmaps that will be automaticaly imported into osu!
-Plus, you can launch an IRC bot from the app that will make all [available commands](./docs/commands.md) usable to peoples pming you and from all the matches chats that the bot is connected to. (how to connect docs soon..) +**Table of contents:** - + -This is still under development. +- [Quick Tour](#quick-tour) +- [Technology](#technology) +- [Development](#development) +- [Download](#download) +- [License](#license) -## Getting Started + + +## Quick Tour +- This App gives you access to all the beatmaps mirrored on [Beatconnect](https://beatconnect.io). You can downloads multiple beatmaps that will be automaticaly imported into osu!
+ + +- You can launch an IRC bot from the app that will make all [available commands](./docs/commands.md) usable to peoples pming you and from all the matches chats that the bot is connected to. (how to connect docs soon..)
+Comming with the autobeat feature that send the Beatconnect download link in the #multuplayer channel each time host change the beatmap + + +- Manage connected matchs + + +## Technology + +- The application is powered by **[Electron](https://electronjs.org), + with [React](https://facebook.github.io/react/), + [Redux](http://redux.js.org/), + and [NodeJs](https://nodejs.org)** + +- The bot uses a slightly modified version of **[node-irc](https://github.com/yadPe/node-irc) to connect to game IRC.** + +## Development These instructions will get you a copy of the project up and running on your local machine. @@ -34,6 +60,9 @@ $ npm run build $ electron . ``` +## Download +- Latest release available [here](https://github.com/yadPe/beatconnect_client/releases/latest) + ## License This project is licensed under the GNU V3.0 License. diff --git a/docs/m3krbwj3sfdG480M.gif b/docs/m3krbwj3sfdG480M.gif new file mode 100644 index 00000000..be40723d Binary files /dev/null and b/docs/m3krbwj3sfdG480M.gif differ diff --git a/package-lock.json b/package-lock.json index b2414ccd..b35a29d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "beatconnect_client", - "version": "0.1.3", + "version": "0.1.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -917,24 +917,6 @@ "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-9.0.1.tgz", "integrity": "sha512-6It2EVfGskxZCQhuykrfnALg7oVeiI6KclWSmGDqB0AiInVrTGB9Jp9i4/Ad21u9Jde/voVQz6eFX/eSg/UsPA==" }, - "@emotion/is-prop-valid": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.2.tgz", - "integrity": "sha512-ZQIMAA2kLUWiUeMZNJDTeCwYRx1l8SQL0kHktze4COT22occKpDML1GDUXP5/sxhOMrZO8vZw773ni4H5Snrsg==", - "requires": { - "@emotion/memoize": "0.7.2" - } - }, - "@emotion/memoize": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.2.tgz", - "integrity": "sha512-hnHhwQzvPCW1QjBWFyBtsETdllOM92BfrKWbUTmh9aeOlcVOiXvlPsK4104xH8NsaKfg86PTFsWkueQeUfMA/w==" - }, - "@emotion/unitless": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.4.tgz", - "integrity": "sha512-kBa+cDHOR9jpRJ+kcGMsysrls0leukrm68DmFQoMIWQcXdr2cZvyvypWuGYT7U+9kAExUE7+T7r6G3C3A6L8MQ==" - }, "@hapi/address": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.0.0.tgz", @@ -1677,12 +1659,6 @@ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==" }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -2257,22 +2233,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.2.tgz", "integrity": "sha512-CxwvxrZ9OirpXQ201Ec57OmGhmI8/ui/GwTDy0hSp6CmRvgRC0pSair6Z04Ck+JStA0sMPZzSJ3uE4n17EXpPQ==" }, - "babel-plugin-styled-components": { - "version": "1.10.6", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.6.tgz", - "integrity": "sha512-gyQj/Zf1kQti66100PhrCRjI5ldjaze9O0M3emXRPAN80Zsf8+e1thpTpaXJXVHXtaM4/+dJEgZHyS9Its+8SA==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-module-imports": "^7.0.0", - "babel-plugin-syntax-jsx": "^6.18.0", - "lodash": "^4.17.11" - } - }, - "babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" - }, "babel-plugin-syntax-object-rest-spread": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", @@ -3036,11 +2996,6 @@ } } }, - "camelize": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", - "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" - }, "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -3065,12 +3020,6 @@ "rsvp": "^4.8.4" } }, - "capture-stack-trace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", - "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", - "dev": true - }, "case-sensitive-paths-webpack-plugin": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.2.0.tgz", @@ -3557,15 +3506,6 @@ "elliptic": "^6.0.0" } }, - "create-error-class": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "dev": true, - "requires": { - "capture-stack-trace": "^1.0.0" - } - }, "create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", @@ -3642,11 +3582,6 @@ "postcss": "^7.0.5" } }, - "css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" - }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -3736,16 +3671,6 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, - "css-to-react-native": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-2.3.1.tgz", - "integrity": "sha512-yO+oEx1Lf+hDKasqQRVrAvzMCz825Huh1VMlEEDlRWyAhFb/FWb6I0KpEF1PkyKQ7NEdcx9d5M2ZEWgJAsgPvQ==", - "requires": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^3.3.0" - } - }, "css-tree": { "version": "1.0.0-alpha.28", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.28.tgz", @@ -5344,11 +5269,6 @@ } } }, - "express-prettify": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/express-prettify/-/express-prettify-0.1.1.tgz", - "integrity": "sha512-AtLYJhseS9bIwkG+tqSn4OKr7RJ8fxJ+/P8SDZ9XdcMiGPvi633c2YC8QaHJKokyffd6sHxSuyZ/eWwDX6kOpw==" - }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -6898,12 +6818,6 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" }, - "ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", - "dev": true - }, "immer": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", @@ -7287,12 +7201,6 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", - "dev": true - }, "is-regex": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", @@ -7311,12 +7219,6 @@ "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" }, - "is-retry-allowed": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", - "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", - "dev": true - }, "is-root": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.0.0.tgz", @@ -7359,11 +7261,6 @@ "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", "dev": true }, - "is-what": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.2.4.tgz", - "integrity": "sha512-0awkPsfVd85bYStP99EqLxKvhc5SiE70hSZCPxJN2SYZ5d+IkX+r1Ri0qnPWPnuRVFrqrEnI3JgFN3yrGtjXaw==" - }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -8776,14 +8673,6 @@ } } }, - "merge-anything": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-2.4.0.tgz", - "integrity": "sha512-MhJcPOEcDUIbwU0LnEfx5S9s9dfQ/KPu4g2UA5T5G1LRKS0XmpDvJ9+UUfTkfhge+nA1gStE4tJAvx6lXLs+rg==", - "requires": { - "is-what": "^3.2.4" - } - }, "merge-deep": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.2.tgz", @@ -9169,218 +9058,6 @@ } } }, - "nodemon": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.1.tgz", - "integrity": "sha512-/DXLzd/GhiaDXXbGId5BzxP1GlsqtMGM9zTmkWrgXtSqjKmGSbLicM/oAy4FR0YWm14jCHRwnR31AHS2dYFHrg==", - "dev": true, - "requires": { - "chokidar": "^2.1.5", - "debug": "^3.1.0", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.6", - "semver": "^5.5.0", - "supports-color": "^5.2.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.2", - "update-notifier": "^2.5.0" - }, - "dependencies": { - "ansi-align": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", - "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", - "dev": true, - "requires": { - "string-width": "^2.0.0" - } - }, - "boxen": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", - "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", - "dev": true, - "requires": { - "ansi-align": "^2.0.0", - "camelcase": "^4.0.0", - "chalk": "^2.0.1", - "cli-boxes": "^1.0.0", - "string-width": "^2.0.0", - "term-size": "^1.2.0", - "widest-line": "^2.0.0" - } - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", - "dev": true - }, - "cli-boxes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", - "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", - "dev": true - }, - "configstore": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", - "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", - "dev": true, - "requires": { - "dot-prop": "^4.1.0", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "unique-string": "^1.0.0", - "write-file-atomic": "^2.0.0", - "xdg-basedir": "^3.0.0" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", - "dev": true, - "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - } - }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "dev": true, - "requires": { - "ci-info": "^1.5.0" - } - }, - "is-npm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", - "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", - "dev": true - }, - "latest-version": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", - "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", - "dev": true, - "requires": { - "package-json": "^4.0.0" - } - }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "package-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", - "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", - "dev": true, - "requires": { - "got": "^6.7.1", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" - } - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true - }, - "registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", - "dev": true, - "requires": { - "rc": "^1.0.1" - } - }, - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - }, - "update-notifier": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", - "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", - "dev": true, - "requires": { - "boxen": "^1.2.1", - "chalk": "^2.0.1", - "configstore": "^3.0.0", - "import-lazy": "^2.1.0", - "is-ci": "^1.0.10", - "is-installed-globally": "^0.1.0", - "is-npm": "^1.0.0", - "latest-version": "^3.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" - } - }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dev": true, - "requires": { - "prepend-http": "^1.0.1" - } - } - } - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "dev": true, - "requires": { - "abbrev": "1" - } - }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -11091,12 +10768,6 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==" }, - "pstree.remy": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", - "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==", - "dev": true - }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -11414,6 +11085,15 @@ "prop-types": "^15.7.2" } }, + "react-window": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.5.tgz", + "integrity": "sha512-HeTwlNa37AFa8MDZFZOKcNEkuF2YflA0hpGPiTT9vR7OawEt+GZbfM6wqkBahD3D3pUjIabQYzsnY/BSJbgq6Q==", + "requires": { + "@babel/runtime": "^7.0.0", + "memoize-one": ">=3.1.1 <6" + } + }, "read-config-file": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-3.2.2.tgz", @@ -12844,26 +12524,6 @@ "schema-utils": "^1.0.0" } }, - "styled-components": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-4.3.2.tgz", - "integrity": "sha512-NppHzIFavZ3TsIU3R1omtddJ0Bv1+j50AKh3ZWyXHuFvJq1I8qkQ5mZ7uQgD89Y8zJNx2qRo6RqAH1BmoVafHw==", - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@emotion/is-prop-valid": "^0.8.1", - "@emotion/unitless": "^0.7.0", - "babel-plugin-styled-components": ">= 1", - "css-to-react-native": "^2.2.2", - "memoize-one": "^5.0.0", - "merge-anything": "^2.2.4", - "prop-types": "^15.5.4", - "react-is": "^16.6.0", - "stylis": "^3.5.0", - "stylis-rule-sheet": "^0.0.10", - "supports-color": "^5.5.0" - } - }, "stylehacks": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", @@ -12886,16 +12546,6 @@ } } }, - "stylis": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", - "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==" - }, - "stylis-rule-sheet": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz", - "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==" - }, "sumchecker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-2.0.2.tgz", @@ -13168,12 +12818,6 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==" }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true - }, "timers-browserify": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", @@ -13249,15 +12893,6 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, - "touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "requires": { - "nopt": "~1.0.10" - } - }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -13391,31 +13026,10 @@ } } }, - "undefsafe": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", - "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", - "dev": true, - "requires": { - "debug": "^2.2.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } + "underscore": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", @@ -13571,12 +13185,6 @@ } } }, - "unzip-response": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", - "dev": true - }, "upath": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", diff --git a/package.json b/package.json index 2973e983..d8f78e31 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "beatconnect_client", "productName": "Beatconnect Client", - "version": "0.1.4", + "version": "0.1.5", "description": "Beatconnect power for osu irc", "author": "yadpe ", "license": "GPL-3.0", - "main": "./public/electron.js", + "main": "./public/electron/electron.js", "homepage": "./", "scripts": { "start": "node scripts/start.js", @@ -49,7 +49,7 @@ "buildResources": "assets" }, "extraMetadata": { - "main": "./build/electron.js", + "main": "./build/electron/electron.js", "prune": true }, "dmg": { @@ -132,15 +132,16 @@ "react-jss": "^8.6.1", "react-redux": "^7.1.0", "react-visibility-sensor": "^5.1.1", + "react-window": "^1.8.5", "redux": "^4.0.1", "resolve": "1.10.0", "sass-loader": "7.1.0", "semver": "6.0.0", "string.prototype.startswith": "^0.2.0", "style-loader": "0.23.1", - "styled-components": "^4.3.2", "terser-webpack-plugin": "1.2.3", "ts-pnp": "1.1.2", + "underscore": "^1.9.1", "update-electron-app": "^1.5.0", "url-loader": "1.1.2", "webpack": "4.29.6", @@ -215,4 +216,4 @@ "react-app" ] } -} \ No newline at end of file +} diff --git a/public/Window.js b/public/electron/Window.js similarity index 90% rename from public/Window.js rename to public/electron/Window.js index d6d74f32..f39ec4f7 100644 --- a/public/Window.js +++ b/public/electron/Window.js @@ -3,11 +3,11 @@ const path = require('path'); // default window settings const defaultProps = { - icon: path.join(__dirname, '/icon.png'), + icon: path.join(__dirname, '../icon.png'), width: 1200, height: 750, minHeight: 350, - minWidth: 850, + minWidth: 890, show: false, darkTheme: true, frame: process.env.ELECTRON_START_URL ? true : false diff --git a/public/electron.js b/public/electron/electron.js similarity index 79% rename from public/electron.js rename to public/electron/electron.js index 98efba64..eb48e0f3 100644 --- a/public/electron.js +++ b/public/electron/electron.js @@ -4,13 +4,12 @@ const isDev = require('electron-is-dev'); const { autoUpdater } = require('electron-updater'); const DownloadManager = require("electron-download-manager"); const Window = require('./Window'); -const path = require('path') -const url = require('url') +const path = require('path'); +const url = require('url'); +require('./ipcMessages'); - -autoUpdater.on('error', (error) => { - log.warn(`Error: \n ${error == null ? "unknown" : (error.stack || error).toString()}`); -}); +log.transports.file.level = "debug"; +autoUpdater.logger = log; autoUpdater.checkForUpdatesAndNotify() DownloadManager.register({ downloadFolder: app.getPath("downloads") + "/beatconnect" @@ -28,12 +27,12 @@ const main = () => { webSecurity: false }, url: isDev ? process.env.ELECTRON_START_URL || url.format({ - pathname: path.join(__dirname, '../build/index.html'), + pathname: path.join(__dirname, '../../build/index.html'), protocol: 'file:', slashes: true }) : url.format({ - pathname: path.join(__dirname, './index.html'), + pathname: path.join(__dirname, '.././index.html'), protocol: 'file:', slashes: true }) diff --git a/public/electron/ipcMessages.js b/public/electron/ipcMessages.js new file mode 100644 index 00000000..7f130a65 --- /dev/null +++ b/public/electron/ipcMessages.js @@ -0,0 +1,13 @@ +const { ipcMain } = require('electron'); +const { join } = require('path'); +const { fork } = require('child_process'); + +ipcMain.on('osuSongsScan', (event, osuDir) => { + const osuSongsScan = fork(join(__dirname, '../../src/helpers/osuSongsScan.js')); + osuSongsScan.send(JSON.stringify({ msg: 'start', osuDir })); + osuSongsScan.on('message', msg => { + const { results, status } = JSON.parse(msg) + if (results) event.reply('osuSongsScanResults', results) + if (status) event.reply('osuSongsScanStatus', status) + }) +}) \ No newline at end of file diff --git a/public/manifest.json b/public/manifest.json index 1f2f141f..683476f6 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "React App", - "name": "Create React App Sample", + "short_name": "Beatconnect", + "name": "Beatconnect Client", "icons": [ { "src": "favicon.ico", @@ -11,5 +11,5 @@ "start_url": ".", "display": "standalone", "theme_color": "#000000", - "background_color": "#ffffff" + "background_color": "#121212" } diff --git a/src/App/App.css b/src/App/App.css index a15a6ece..89cdd602 100644 --- a/src/App/App.css +++ b/src/App/App.css @@ -17,21 +17,6 @@ body { user-select: none; } -.menuContainer { - text-align: center; - /* display: flex; - flex-direction: column; - align-items: center; */ - /* justify-content: center; */ - font-size: calc(10px + 2vmin); - color: white; - background-color: #121212; - text-rendering: optimizelegibility; - font-family: Open Sans, sans-serif; - height: calc(100vh - 79px); - overflow-y: auto; -} - .Beatmap { width: 80%; background: #2a2a2a; @@ -133,7 +118,7 @@ body { .Nav div, a, svg { - transition: all 220ms; + transition: all 200ms; } .separator { @@ -144,22 +129,22 @@ svg { } /* webkit scroll */ -.menuContainer::-webkit-scrollbar { +.customScroll::-webkit-scrollbar { width: 8px; } -.menuContainer::-webkit-scrollbar-track { +.customScroll::-webkit-scrollbar-track { background: #2a2a2a; } -.menuContainer::-webkit-scrollbar-thumb { +.customScroll::-webkit-scrollbar-thumb { background: #00965f; } -.downloadMenu{ +/* .downloadMenu{ width: 80%; margin: 0 auto; -} +} */ /* p{ font-size: 50%; diff --git a/src/App/components/Browse/Search.js b/src/App/components/Beatmaps/Search.js similarity index 53% rename from src/App/components/Browse/Search.js rename to src/App/components/Beatmaps/Search.js index fdd647e2..cb695d20 100644 --- a/src/App/components/Browse/Search.js +++ b/src/App/components/Beatmaps/Search.js @@ -1,38 +1,66 @@ import React, { useState, useEffect } from 'react'; -import { Button, Text, ProgressCircle } from 'react-desktop/windows'; +import { Button, ProgressCircle } from 'react-desktop/windows'; import TextInput from '../common/TextInput' import askBeatconnect from './askBeatconnect' import DropDown from '../common/DropDown'; +import renderIcons from '../../utils/renderIcons'; +import _ from 'underscore'; +import { connect } from 'react-redux'; const availableStatus = ['ranked', 'approved', 'qualified', 'loved', 'unranked', 'all']; const availableModes = ['all', 'std', 'mania', 'taiko', 'ctb'] -const Search = ({ theme, lastSearch }) => { +const Search = ({ theme, lastSearch, isBusy, beatmapCount }) => { const [search, setSearch] = useState(lastSearch); const [isLoading, setIsLoading] = useState(false); - const searchOnEnter = (e) => { if (e.keyCode === 13) { - askBeatconnect(search, setIsLoading) + execSearch() + } + } + const execSearch = (force) => { + if (!_.isEqual(lastSearch, search) || force) { + askBeatconnect(search, setIsLoading, true) } } useEffect(() => { - if (search.query === '') { - askBeatconnect(search, setIsLoading) - } + if (beatmapCount === 0 || (lastSearch.status !== search.status || lastSearch.mode !== search.mode)) execSearch(true) }, [search]) return ( + { setSearch({ ...search, mode: e.target.value }); askBeatconnect({ ...search, mode: e.target.value }, setIsLoading) }} /> + onSelect={(e) => { + setSearch({ ...search, mode: e.target.value }) + execSearch() + }} /> { setSearch({ ...search, status: e.target.value }); askBeatconnect({ ...search, status: e.target.value }, setIsLoading) }} /> + onSelect={(e) => { + setSearch({ ...search, status: e.target.value }) + execSearch() + }} /> { value={search.query} onChange={e => setSearch({ ...search, query: e.target.value })} onKeyDown={searchOnEnter} - onBlur={() => askBeatconnect(search, setIsLoading)} + onBlur={execSearch} /> - ); } -export default Search; \ No newline at end of file +const mapStateToProps = ({ main }) => ({ beatmapCount: main.searchResults.beatmaps.length }) +export default connect(mapStateToProps)(Search); \ No newline at end of file diff --git a/src/App/components/Beatmaps/askBeatconnect.js b/src/App/components/Beatmaps/askBeatconnect.js new file mode 100644 index 00000000..dd2eb848 --- /dev/null +++ b/src/App/components/Beatmaps/askBeatconnect.js @@ -0,0 +1,20 @@ +import store from '../../../store'; +import _ from 'underscore'; + +const askBeatconnect = (search, onLoading, resetPage) => { + const { query, status, mode } = search; + let { page } = search + if (resetPage) page = 0 + const prevBeatmaps = store.getState().main.searchResults.beatmaps + onLoading(true) + const formatQuery = query.split(' ').join('%20') + fetch(`https://beatconnect.io/api/search/?token=b3z8gl9pzt7iqa89&p=${page || 0}&q=${formatQuery}&s=${status || 'ranked'}&m=${mode || 'all'}`) + .then(res => res.json()) + .then(({ beatmaps, max_page }) => { + if (page > 0) beatmaps = _.union(prevBeatmaps, beatmaps) + store.dispatch({ type: 'SEARCH_RESULTS', searchResults: { search, beatmaps: beatmaps || [], max_page, page: page || 0 }}) + onLoading(false) + }) +} + +export default askBeatconnect \ No newline at end of file diff --git a/src/App/components/Beatmaps/index.js b/src/App/components/Beatmaps/index.js new file mode 100644 index 00000000..9b4119cf --- /dev/null +++ b/src/App/components/Beatmaps/index.js @@ -0,0 +1,91 @@ +import React, { useEffect, memo, useState, useRef } from 'react'; +import { connect } from 'react-redux'; +import { FixedSizeGrid } from 'react-window'; +import injectSheet from 'react-jss'; +import Beatmap from '../common/Beatmap' +import Search from './Search'; +import askBeatconnect from './askBeatconnect'; +import store from '../../../store'; + +const styles = { + list: { + // display: 'grid', + // alignItems: 'center', + // gridTemplateColumns: 'repeat(auto-fit, minmax(700px, 1fr))', + // //gridGap: '10px' + } +}; + +const Beatmaps = ({ theme, searchResults, classes, setHeaderContent, window, panelExpended }) => { + const [isLoading, setIsloading] = useState(false); + const { search, beatmaps, lastScroll } = searchResults + let { page } = searchResults + const canLoadMore = beatmaps.length % 50 === 0 + const lastScrollPosition = useRef(lastScroll || 0) + if (lastScroll) lastScrollPosition.current = lastScroll + const gridWidth = (window.width - (panelExpended ? 150 : 48)) + const gridHeight = window.height - 79 + const displayGrid = gridWidth >= 1200; + const rowCount = displayGrid ? beatmaps.length / 2 : beatmaps.length + const onScroll = ({ scrollTop }) => lastScrollPosition.current = scrollTop + const loadMore = () => { + if (isLoading) return console.log('NOPE') + console.log('MORE') + askBeatconnect({ ...search, page: page += 1 }, setIsloading) + } + const newItemsRendered = ({ + overscanRowStopIndex, + overscanColumnStopIndex + }) => { + // const visibleStartIndex = overscanRowStartIndex * overscanColumnStopIndex; + const visibleStopIndex = displayGrid ? (overscanRowStopIndex * overscanColumnStopIndex) : overscanRowStopIndex; + + if (visibleStopIndex === rowCount - 3 && canLoadMore) { + loadMore() + } + }; + + useEffect(() => { + setHeaderContent() + return () => setHeaderContent(null) + }, [setHeaderContent, search, theme, isLoading]) + + useEffect(() => { + return () => store.dispatch({type: 'SAVEBEATMAPSSCROLLPOS', payload: lastScrollPosition.current}) + }, []) + + const renderBeatmaps = ({ columnIndex, rowIndex, style }) => { + return ( +
+ {displayGrid ? + + : + } +
+ ) + } + + return ( +
+ + {renderBeatmaps} + +
+ ); +} + +const areEqual = (prevProps, nextProps) => (JSON.stringify(prevProps.searchResults) === JSON.stringify(nextProps.searchResults)) && (JSON.stringify(prevProps.window)) === (JSON.stringify(nextProps.window)) && (prevProps.panelExpended === nextProps.panelExpended) +const mapStateToProps = ({ main, settings }) => ({ searchResults: main.searchResults, window: main.window, panelExpended: settings.userPreferences.sidePanelExpended }) +export default connect(mapStateToProps)(memo(injectSheet(styles)(Beatmaps), areEqual)); \ No newline at end of file diff --git a/src/App/components/Matchs/AddMatch.js b/src/App/components/Bot/Matchs/AddMatch.js similarity index 87% rename from src/App/components/Matchs/AddMatch.js rename to src/App/components/Bot/Matchs/AddMatch.js index d1cf8b2b..67c03f15 100644 --- a/src/App/components/Matchs/AddMatch.js +++ b/src/App/components/Bot/Matchs/AddMatch.js @@ -1,9 +1,9 @@ import React, { useState } from 'react'; import { Button, Text } from 'react-desktop/windows'; -import TextInput from '../common/TextInput'; +import TextInput from '../../common/TextInput'; import { connect } from 'react-redux'; -const AddMatch = ({ bot, theme, errors, ircUsername }) => { +const AddMatch = ({ bot, theme, errors, ircUsername, connected }) => { const [ reqMatchId, setReqMatchId ] = useState(''); const error = errors.filter(id => id === reqMatchId).length === 1 return ( @@ -19,7 +19,7 @@ const AddMatch = ({ bot, theme, errors, ircUsername }) => { className='btn' push color={theme.color} - hidden={!bot.joinMatch} + hidden={!(connected && connected !== 'connecting')} onClick={() => bot.joinMatch(reqMatchId)} > Join diff --git a/src/App/components/Matchs/MatchDetails/ControlsBar.js b/src/App/components/Bot/Matchs/MatchDetails/ControlsBar.js similarity index 92% rename from src/App/components/Matchs/MatchDetails/ControlsBar.js rename to src/App/components/Bot/Matchs/MatchDetails/ControlsBar.js index 8e7a9e99..f4c485ce 100644 --- a/src/App/components/Matchs/MatchDetails/ControlsBar.js +++ b/src/App/components/Bot/Matchs/MatchDetails/ControlsBar.js @@ -1,78 +1,78 @@ -import React from 'react' -import injectSheet from 'react-jss'; -import { Button } from 'react-desktop/windows'; -import renderIcons from '../../../utils/renderIcons'; - -const styles = { - ControlsBar: { - padding: 0, - listStyle: 'none', - display: 'flex', - backgroundColor: 'transparent', - margin: 0, - userSelect: 'none', - }, - titleContainer: { - marginLeft: 'auto', - marginRight: 'auto', - }, - title: { - fontSize: '90%', - margin: 0, - maxWidth: '100vmin', - overflow: 'hidden', - } -}; - -const ControlsBar = ({ classes, theme, match, close }) => { - return ( - - ); -} - +import React from 'react' +import injectSheet from 'react-jss'; +import { Button } from 'react-desktop/windows'; +import renderIcons from '../../../../utils/renderIcons'; + +const styles = { + ControlsBar: { + padding: 0, + listStyle: 'none', + display: 'flex', + backgroundColor: 'transparent', + margin: 0, + userSelect: 'none', + }, + titleContainer: { + marginLeft: 'auto', + marginRight: 'auto', + }, + title: { + fontSize: '90%', + margin: 0, + maxWidth: '100vmin', + overflow: 'hidden', + } +}; + +const ControlsBar = ({ classes, theme, match, close }) => { + return ( + + ); +} + export default injectSheet(styles)(ControlsBar); \ No newline at end of file diff --git a/src/App/components/Matchs/MatchDetails/Player.js b/src/App/components/Bot/Matchs/MatchDetails/Player.js similarity index 93% rename from src/App/components/Matchs/MatchDetails/Player.js rename to src/App/components/Bot/Matchs/MatchDetails/Player.js index bac5df82..ab517168 100644 --- a/src/App/components/Matchs/MatchDetails/Player.js +++ b/src/App/components/Bot/Matchs/MatchDetails/Player.js @@ -1,58 +1,57 @@ -import React from 'react'; -import { Button, Text } from 'react-desktop/windows'; -import injectSheet from 'react-jss'; - -const styles = { - Player: { - padding: 0, - listStyle: 'none', - display: 'flex', - backgroundColor: '#2a2a2a', - margin: '0 auto', - width: '100%', - transitionProperty: 'filter', - transitionDuration: '200ms', - '&:hover': { - filter: 'brightness(1.2)' - } - }, - playerNameContainer: { - margin: '0 auto 0 0' - }, - playerName: { - fontSize: '62%', - margin: '1vmin' - } -}; - -const Player = ({ classes, theme, playerInfos, match }) => { - console.log(playerInfos, match.host) - return ( - - ); -} - +import React from 'react'; +import { Button, Text } from 'react-desktop/windows'; +import injectSheet from 'react-jss'; + +const styles = { + Player: { + padding: 0, + listStyle: 'none', + display: 'flex', + backgroundColor: '#2a2a2a', + margin: '0 auto', + width: '100%', + transitionProperty: 'filter', + transitionDuration: '200ms', + '&:hover': { + filter: 'brightness(1.2)' + } + }, + playerNameContainer: { + margin: '0 auto 0 0' + }, + playerName: { + fontSize: '62%', + margin: '1vmin' + } +}; + +const Player = ({ classes, theme, playerInfos, match }) => { + return ( + + ); +} + export default injectSheet(styles)(Player); \ No newline at end of file diff --git a/src/App/components/Matchs/MatchDetails/PlayersList.js b/src/App/components/Bot/Matchs/MatchDetails/PlayersList.js similarity index 100% rename from src/App/components/Matchs/MatchDetails/PlayersList.js rename to src/App/components/Bot/Matchs/MatchDetails/PlayersList.js diff --git a/src/App/components/Matchs/MatchDetails/index.js b/src/App/components/Bot/Matchs/MatchDetails/index.js similarity index 85% rename from src/App/components/Matchs/MatchDetails/index.js rename to src/App/components/Bot/Matchs/MatchDetails/index.js index 5ebf691d..008b6ee2 100644 --- a/src/App/components/Matchs/MatchDetails/index.js +++ b/src/App/components/Bot/Matchs/MatchDetails/index.js @@ -1,6 +1,6 @@ -import React from 'react' -import Beatmap from '../../common/Beatmap' -import PlayersList from './PlayersList' +import React from 'react'; +import Beatmap from '../../../common/Beatmap'; +import PlayersList from './PlayersList'; import ControlsBar from './ControlsBar'; const MatchDetails = ({ match, theme, close }) => { @@ -9,6 +9,8 @@ const MatchDetails = ({ match, theme, close }) => { // // )) + console.log('update Match', match) + return (
diff --git a/src/App/components/Matchs/MatchListItem.js b/src/App/components/Bot/Matchs/MatchListItem.js similarity index 74% rename from src/App/components/Matchs/MatchListItem.js rename to src/App/components/Bot/Matchs/MatchListItem.js index 8ac7c9db..dd9d5203 100644 --- a/src/App/components/Matchs/MatchListItem.js +++ b/src/App/components/Bot/Matchs/MatchListItem.js @@ -1,44 +1,43 @@ -import React from 'react'; -import MatchDetails from './MatchDetails' -import injectSheet from 'react-jss'; -import Cover from '../common/Beatmap/Cover'; - -const styles = { - MatchListItem: { - width: '80%', - margin: '10px auto', - padding: 0, - listStyle: 'none', - display: 'flex', - backgroundColor: '#2a2a2a', - userSelect: 'none', - '&:hover': { - filter: 'brightness(1.1)' - } - }, - matchName: { - margin: 'auto' - }, - playersNum: { - margin: 'auto 1vmin auto 1vmin', - fontSize: '50%', - } -}; -const MatchListItem = ({ classes, match, theme, setSelected }) => { - const closeMatchItem = () => setSelected(null) - return ( -
    setSelected()}> -
  • - -
  • -
  • - {match.matchName} -
  • -
  • - {`${match.players.length} players`} -
  • -
- ); -} - +import React from 'react'; +import injectSheet from 'react-jss'; +import Cover from '../../common/Beatmap/Cover'; + +const styles = { + MatchListItem: { + height: '60px', + width: '80%', + margin: '10px auto', + padding: 0, + listStyle: 'none', + display: 'flex', + backgroundColor: '#2a2a2a', + userSelect: 'none', + '&:hover': { + filter: 'brightness(1.1)' + } + }, + matchName: { + margin: 'auto' + }, + playersNum: { + margin: 'auto 1vmin auto 1vmin', + fontSize: '50%', + } +}; +const MatchListItem = ({ classes, match, theme, setSelected }) => { + return ( // setSelected( setSelected(null)} />) +
    setSelected(match.id)}> +
  • + +
  • +
  • + {match.matchName} +
  • +
  • + {`${match.players.length} players`} +
  • +
+ ); +} + export default injectSheet(styles)(MatchListItem); \ No newline at end of file diff --git a/src/App/components/Bot/Matchs/index.js b/src/App/components/Bot/Matchs/index.js new file mode 100644 index 00000000..63e56e3f --- /dev/null +++ b/src/App/components/Bot/Matchs/index.js @@ -0,0 +1,49 @@ +// Affiche la liste des matchs si aucun match n'est selectionné + +import React, { useState } from 'react' +import AddMatch from './AddMatch' +import MatchListItem from './MatchListItem' +import { connect } from 'react-redux'; +import MatchDetails from './MatchDetails'; + +const renderMatchsList = (mpMatchs, bot, theme, setSelected, connected) => { + console.log('update matchsList', mpMatchs) + // mpMatchs = new Array(20).fill({matchName: 'test', players: new Array(16).fill('PEPPY')}) + if (mpMatchs.length > 0) return ( + + + {mpMatchs.map(match => )} + + ) + return ( + + + {connected && connected !== 'connecting' ?

Not connected to any match

:

Please start the bot before connecting to a match

} +
+ ) +} + +const Matchs = ({ mpMatchs, theme, bot, connected }) => { + const [selectedMatch, setSelectedMatch] = useState(null) + console.log('selectedMatch', selectedMatch) + + const renderSelectedMatch = () => { + const currentMatch = mpMatchs.map(match => match.id === selectedMatch ? setSelectedMatch(null)} /> : null) + if (currentMatch.length === 1) return currentMatch + return setSelectedMatch(null) + } + // useEffect(() => { + // if (selectedMatch){ + // if (mpMatchs.filter(match => selectedMatch.id === match.id).length === 0) setSelectedMatch(null) + // } + // }, [mpMatchs]) + + return ( +
+ {selectedMatch ? renderSelectedMatch() : renderMatchsList(mpMatchs, bot, theme, setSelectedMatch, connected)} +
+ ) +}; + +const mapStateToProps = ({ main }) => ({ mpMatchs: main.mpMatchs }) +export default connect(mapStateToProps)(Matchs); diff --git a/src/App/components/Bot/Start.js b/src/App/components/Bot/Start.js new file mode 100644 index 00000000..da459b41 --- /dev/null +++ b/src/App/components/Bot/Start.js @@ -0,0 +1,49 @@ +import React from 'react' +import start from '../../../Bot'; +import { connect } from 'react-redux'; +import Toggle from '../common/Toggle'; +import injectSheet from 'react-jss'; + +const styles = { + Start: { + transition: 'background 0ms', + display: 'flex', + 'div p ': { + margin: '10px' + } + } +}; + +const Start = ({ classes, connected, theme, irc, osuApi }) => { + const notReady = (!osuApi || !irc.username || !irc.password) + + return ( +
+ + +

+ {connected ? connected === 'connecting' ? + 'Connecting to Bancho via IRC..' + : 'Online' + : 'Offline' + } +

+ { + notReady ? +

+ Warning: no irc credential or osu API key found. Please go to settings section +

: null + } +
+ ) +}; + +const mapStateToProps = ({ settings }) => ({ irc: settings.userPreferences.irc, osuApi: settings.userPreferences.osuApi.key }); +export default connect(mapStateToProps)(injectSheet(styles)(Start)); \ No newline at end of file diff --git a/src/App/components/Bot/index.js b/src/App/components/Bot/index.js new file mode 100644 index 00000000..755cd5a3 --- /dev/null +++ b/src/App/components/Bot/index.js @@ -0,0 +1,18 @@ +import React, { useEffect } from 'react'; +import Start from './Start'; +import Matchs from './Matchs' + +const Bot = ({ connected, bot, theme, setHeaderContent }) => { + useEffect(() => { + setHeaderContent() + return () => setHeaderContent(null) + }, [setHeaderContent, connected, theme]) + + return ( +
+ +
+ ); +} + +export default Bot; \ No newline at end of file diff --git a/src/App/components/Browse/askBeatconnect.js b/src/App/components/Browse/askBeatconnect.js deleted file mode 100644 index 7f3813fc..00000000 --- a/src/App/components/Browse/askBeatconnect.js +++ /dev/null @@ -1,16 +0,0 @@ -import store from '../../../store'; - -const askBeatconnect = (search, onLoading) => { - const { query, page, status, mode } = search; - console.log(status) - onLoading(true) - const formatQuery = query.split(' ').join('%20') - fetch(`https://beatconnect.io/api//search/?token=b3z8gl9pzt7iqa89&p=${page || 0}&q=${formatQuery}&s=${status || 'ranked'}&m=${mode || 'all'}`) - .then(res => res.json()) - .then(({ beatmaps, max_page }) => { - store.dispatch({ type: 'SEARCH_RESULTS', searchResults: { search, beatmaps, max_page, page }}) - onLoading(false) - }) -} - -export default askBeatconnect \ No newline at end of file diff --git a/src/App/components/Browse/index.js b/src/App/components/Browse/index.js deleted file mode 100644 index c7f9a977..00000000 --- a/src/App/components/Browse/index.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import Beatmap from '../common/Beatmap' -import Search from './Search'; -import VizSensor from 'react-visibility-sensor'; - - -const Browse = ({ theme, searchResults }) => { - const { search, beatmaps } = searchResults - const that = React.createRef(); - - const renderBeatmaps = () => { - return beatmaps.map((beatmap, i) => { - return beatmaps.length - i === 5 ? - console.log('yeeeeeaa')} - key={`fetchTrigger${beatmap.beatmapset_id || beatmap.id}`}> - - : - - }) - } - - return ( -
- - {renderBeatmaps()} -
- ); -} - -const mapStateToProps = ({ main }) => ({ searchResults: main.searchResults }) -export default connect(mapStateToProps)(Browse); \ No newline at end of file diff --git a/src/App/components/Downloads/DownloadedItems.js b/src/App/components/Downloads/DownloadedItems.js index 5978ad9a..d3d38ed0 100644 --- a/src/App/components/Downloads/DownloadedItems.js +++ b/src/App/components/Downloads/DownloadedItems.js @@ -1,21 +1,38 @@ -import React, { useContext } from 'react' +import React, { useContext } from 'react'; +import { FixedSizeList } from 'react-window'; import { HistoryContext } from '../../../Providers/HistoryProvider'; +import { connect } from 'react-redux'; import DownloadsItem from './Item'; -const DownloadedItems = ({ theme }) => { - console.log('DownloadedItems updated') +const DownloadedItems = ({ theme, window }) => { const { history } = useContext(HistoryContext); - const items = []; - for (let item in history) { - const { id, date, name } = history[item]; - items.push(); - } + const items = []; + for (let item in history) { + const { id, date, name } = history[item]; + items.push(); + } items.sort((a, b) => b.props.date - a.props.date); + + const renderItems = ({index, style}) => ( +
+ {items[index]} +
+ ) return ( -
- {items.length > 0 ? items : 'The beatmaps you download will go here'} +
+ + {items.length > 0 ? renderItems : 'The beatmaps you download will go here'} +
); } -export default DownloadedItems; \ No newline at end of file +const mapStateToProps = ({ main }) => ({ window: main.window }); +export default connect(mapStateToProps)(DownloadedItems); \ No newline at end of file diff --git a/src/App/components/Downloads/DownloadsInProgress.js b/src/App/components/Downloads/DownloadsInProgress.js index c481c7b8..8f87656b 100644 --- a/src/App/components/Downloads/DownloadsInProgress.js +++ b/src/App/components/Downloads/DownloadsInProgress.js @@ -1,15 +1,63 @@ -import React, { useContext } from 'react' -import DownloadsItem from './Item'; +import React, { useContext, useState } from 'react' +import injectSheet from 'react-jss'; +import { Button } from 'react-desktop'; import { DownloadQueueContext } from '../../../Providers/DownloadQueueProvider' +import renderIcons from '../../utils/renderIcons'; -const DownloadsInProgress = ({ theme }) => { +const styles = { + DownloadsInProgress: { + display: 'inline-flex', + width: '100%', + '& p:last-of-type': { + margin: 'auto 5px' + }, + '& p:first-of-type': { + marginRight: 'auto' + } + } +}; + +const DownloadsInProgress = ({ theme, classes }) => { const { cancelDownload, currentDownload } = useContext(DownloadQueueContext); + const [isPaused, setIsPaused] = useState(false); + const { infos, progress, item } = currentDownload; + const toggleDownload = () => { + if (!item) { + return alert("If your download is stuck or won't start try restating the app. Sorry for the inconvenience") + } + item.isPaused() ? item.resume() : item.pause() + setIsPaused(item.isPaused()) + } const renderDownloads = () => { - const { infos, progress, item } = currentDownload; if (!infos) return null; return ( -
- +
+

{infos.fullTitle}

+ { + progress ? + ( + +

{`${Math.round(progress.progress)}% @`}

+

{progress.speed}

+
+ ) + : null + } + + +
) } @@ -20,4 +68,4 @@ const DownloadsInProgress = ({ theme }) => { ); } -export default DownloadsInProgress; \ No newline at end of file +export default injectSheet(styles)(DownloadsInProgress); \ No newline at end of file diff --git a/src/App/components/Downloads/DownloadsInQueue.js b/src/App/components/Downloads/DownloadsInQueue.js index 0fcee7ea..b3300fc9 100644 --- a/src/App/components/Downloads/DownloadsInQueue.js +++ b/src/App/components/Downloads/DownloadsInQueue.js @@ -1,9 +1,8 @@ -import React, { useContext } from 'react'; +import React from 'react'; import DownloadsItem from './Item'; -import { DownloadQueueContext } from '../../../Providers/DownloadQueueProvider' -const DownloadsInQueue = ({ theme }) => { - const { queue, removeItemfromQueue } = useContext(DownloadQueueContext); +const DownloadsInQueue = ({ theme, DownloadQueue }) => { + const { queue, removeItemfromQueue } = DownloadQueue; const renderDownloads = () => { if (queue.length === 0) return null diff --git a/src/App/components/Downloads/Item.js b/src/App/components/Downloads/Item.js index d900addb..adeb82de 100644 --- a/src/App/components/Downloads/Item.js +++ b/src/App/components/Downloads/Item.js @@ -20,7 +20,7 @@ const DownloadsItem = ({ id, name, item, date, theme, status, progress, speed, c return (
- +
{ progress ? diff --git a/src/App/components/Downloads/ItemStyles.js b/src/App/components/Downloads/ItemStyles.js index 400a2157..fe5f3377 100644 --- a/src/App/components/Downloads/ItemStyles.js +++ b/src/App/components/Downloads/ItemStyles.js @@ -5,13 +5,15 @@ export default { position: 'relative', margin: '5px auto', textAlign: 'left', - color: '#fff' + color: '#fff', + height: '130', + width: '90%' }, fade: { top: 0, left: 0, width: '100%', - filter: props => props.status === 'downloaded' ? 'brightness(0.3)' : `blur(${props.progress ? convertRange(props.progress, 0, 100, 6, 0) : 5}px) brightness(${props.progress ? convertRange(props.progress, 0, 100, 0.5, 1) : 0.5})`, + filter: props => props.status === 'downloaded' ? 'brightness(0.3)' : `blur(${props.progress ? convertRange(props.progress, 0, 100, 5, 0) : 4}px) brightness(${props.progress ? convertRange(props.progress, 0, 100, 0.5, 1) : 0.5})`, backgroundColor: 'rgba(0, 0, 0, 1)', '&:hover': { filter: props => props.status === 'downloaded' ? ' brightness(0.9)' : '' diff --git a/src/App/components/Downloads/index.js b/src/App/components/Downloads/index.js index 851d03db..61298bf3 100644 --- a/src/App/components/Downloads/index.js +++ b/src/App/components/Downloads/index.js @@ -1,15 +1,47 @@ -import React from 'react' +import React, { useEffect, useState, cloneElement, useContext } from 'react' import DownloadedItems from './DownloadedItems'; import DownloadsInQueue from './DownloadsInQueue'; import DownloadsInProgress from './DownloadsInProgress'; +import NavPanelItem from '../common/NavPanel/Item'; +import NavPanel from '../common/NavPanel'; +import { DownloadQueueContext } from '../../../Providers/DownloadQueueProvider' + +const Downloads = ({ theme, setHeaderContent }) => { + const DownloadQueue = useContext(DownloadQueueContext); + const { queue } = DownloadQueue; + const queueActive = queue.length !== 0; + const [selected, setSelected] = useState(queueActive ? `Queued` : 'Downloaded'); + + useEffect(() => { + setHeaderContent() + return () => setHeaderContent(null) + }, [setHeaderContent, theme]) + + const renderItem = (title, content) => ( + setSelected(title)} + padding="10px 20px" + > + {setHeader => cloneElement(content, { setHeaderContent: setHeader })} + + ); -const Downloads = ({ theme }) => { return ( -
- - + + {renderItem(`Queued`, )} + {renderItem('Downloaded', )} +
); diff --git a/src/App/components/Matchs/index.js b/src/App/components/Matchs/index.js deleted file mode 100644 index 17c200ed..00000000 --- a/src/App/components/Matchs/index.js +++ /dev/null @@ -1,39 +0,0 @@ -// Affiche la liste des matchs si aucun match n'est selectionné - -import React, { useState } from 'react' -import AddMatch from './AddMatch' -import MatchListItem from './MatchListItem' - -const renderMatchsList = (matchs, bot, theme, setSelected) => { - // matchs = new Array(20).fill({matchName: 'test', players: new Array(16).fill('PEPPY')}) - if (matchs.length > 0) return ( - - - {matchs.map(match => )} - - ) - return ( - - - {bot.joinMatch ?

Not connected to any match

:

Please start the bot before connecting to a match

} -
- ) -} - -const Matchs = ({ matchs, theme, bot }) => { - const [selectedMatch, setSelectedMatch] = useState(null) - - // useEffect(() => { - // if (selectedMatch){ - // if (matchs.filter(match => selectedMatch.id === match.id).length === 0) setSelectedMatch(null) - // } - // }, [matchs]) - - return ( -
- {selectedMatch || renderMatchsList(matchs, bot, theme, setSelectedMatch)} -
- ) -}; - -export default Matchs; diff --git a/src/App/components/Nav.js b/src/App/components/Nav.js index c373b52f..9eebf10d 100644 --- a/src/App/components/Nav.js +++ b/src/App/components/Nav.js @@ -1,42 +1,50 @@ -import React, { useState } from 'react'; +import React, { useState, cloneElement } from 'react'; import { connect } from 'react-redux'; -import { NavPane, NavPaneItem } from 'react-desktop/windows'; -import Start from './Start' -import Matchs from './Matchs' -import Browse from './Browse' +import Bot from './Bot' +import Beatmaps from './Beatmaps' import Settings from './Settings' import Downloads from './Downloads'; import renderIcon from '../utils/renderIcons'; +import NavPanel from './common/NavPanel'; +import NavPanelItem from './common/NavPanel/Item'; +import store from '../../store'; -const Nav = ({ mpMatchs, theme, connected, bot }) => { - const [selected, setSelected] = useState('Start'); +const Nav = ({ theme, connected, bot, sidePanelExpended, activeSection }) => { + // const [selected, setSelected] = useState(activeSection); const renderItem = (title, content) => ( - setSelected(title)} + selected={activeSection === title} + onSelect={() => store.dispatch({type: 'UPDATEACTIVESECTION', payload: title})} padding="10px 20px" - push + header > - {content} - + {setHeader => cloneElement(content, { setHeaderContent: setHeader })} + ); return ( - - {renderItem('Start', )} - {renderItem('Matchs', )} - {renderItem('Browse', )} + store.dispatch({ type: 'SIDEPANELEXPENDED', payload: expended })} + volume + tasks + expendable + theme={theme} + > + {renderItem('Beatmaps', )} {renderItem('Downloads', )} - {renderItem('Settings', )} - + {renderItem('Bot', )} + {renderItem('Settings', )} + ); } -const mapStateToProps = ({ main }) => ({ ...main }); +const mapStateToProps = ({ main, settings }) => ({ ...main, sidePanelExpended: settings.userPreferences.sidePanelExpended }); export default connect(mapStateToProps)(Nav); diff --git a/src/App/components/Settings/ConfLoader.js b/src/App/components/Settings/ConfLoader.js index 6aa2558a..87fe1e06 100644 --- a/src/App/components/Settings/ConfLoader.js +++ b/src/App/components/Settings/ConfLoader.js @@ -11,6 +11,7 @@ class ConfLoader { } catch (err) { // assume conf file does not exist this.conf = baseConf } + console.log('confLoader', this.conf) } save = () => { diff --git a/src/App/components/Settings/Configuration.js b/src/App/components/Settings/Configuration.js deleted file mode 100644 index a2c919bd..00000000 --- a/src/App/components/Settings/Configuration.js +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react' -import TextInput from '../common/TextInput'; -import { setIrcUser, setIrcPass, setIRCIsBot, setOSUApiKey, setPrefix, setAutoBeat, setAutoImport } from './actions'; -import injectSheet from 'react-jss'; - -const styles = { - Configuration: { - width: '80%', - margin: '0 auto', - display: 'flex', - justifyContent: 'space-evenly' - } -}; - -const Configuration = ({ classes, theme, values }) => { - const { irc, osuApi, prefix, autoBeat, autoImport } = values; - const items = [ - { name: 'irc user', value: irc.username, action: setIrcUser, type: String }, - { name: 'irc password', value: irc.password, action: setIrcPass, type: String, pass: true }, - { name: 'special bot account', value: irc.isBotAccount, action: setIRCIsBot, type: Boolean }, - { name: 'osu api key', value: osuApi.key, action: setOSUApiKey, type: String, pass: true }, - { name: 'bot prefix', value: prefix, action: setPrefix, type: String }, - { name: 'auto import maps', value: autoImport, action: setAutoImport, type: Boolean }, - // { name: 'autoBeat', value: autoBeat, action: setAutoBeat, type: Boolean } - ] - const renderFields = () => { - return items.map(item => { - if (item.type === String) { - return ( -
-

{item.name}

- item.action(e.target.value)} - /> -
- ) - } - if (item.type === Boolean) { - return ( -
-

{item.name}

- item.action(e.target.checked)} - /> -
- ) - } - }) - } - return ( - -

Configuration

-
- {renderFields()} -
-
- ); -} - -export default injectSheet(styles)(Configuration); \ No newline at end of file diff --git a/src/App/components/Settings/History.js b/src/App/components/Settings/History.js deleted file mode 100644 index f6efee10..00000000 --- a/src/App/components/Settings/History.js +++ /dev/null @@ -1,22 +0,0 @@ -import React, { useContext } from 'react'; -import { HistoryContext } from '../../../Providers/HistoryProvider'; -import { Button } from 'react-desktop/windows'; - -const History = ({ theme }) => { - const history = useContext(HistoryContext) - return ( - -

History

- -
- ); -} - -export default History; \ No newline at end of file diff --git a/src/App/components/Settings/Setting.js b/src/App/components/Settings/Setting.js new file mode 100644 index 00000000..2cf04379 --- /dev/null +++ b/src/App/components/Settings/Setting.js @@ -0,0 +1,153 @@ +import React from 'react' +import TextInput from '../common/TextInput'; +import injectSheet from 'react-jss'; +import Toggle from '../common/Toggle'; +import { Button } from 'react-desktop/windows'; +import DropDown from '../common/DropDown'; + +const styles = { + Setting: { + margin: '0 4vmin', + paddingTop: '20px', + justifyContent: 'space-evenly', + '& p': { + fontSize: '1.05rem', + textAlign: 'left', + margin: 0, + marginLeft: 5 + } + }, + subCategory: { + padding: '20px 20px 20px 20px', + display: 'flex', + flex: 3, + flexWrap: 'wrap', + border: '1px solid #2a2a2a', + borderRadius: '5px', + margin: '15px 0', + '&:hover': { + backgroundColor: 'rgba(255, 255, 255, 0.04)', + }, + '& > :last-child': { + marginRight: '0px', + order: 1 + }, + '& div': { + marginRight: '20px', + '& p': { + fontWeight: '500', + } + } + }, + Toggle: { + display: 'inline-flex', + margin: '0px auto 10px auto', + width: '100%', + backgroundColor: 'rgba(255,255,255,0.02)', + padding: '5px', + borderRadius: '5px', + // cursor: 'pointer', + marginRight: '0px !important', + '& label': { + margin: 'auto 10px', + } + } +}; + +const Setting = ({ classes, theme, settingCategory }) => { + + const renderFields = () => { + return Object.keys(settingCategory).map(subCategory => { + return ( + +

{subCategory.toUpperCase()}

+
+ { + settingCategory[subCategory].map(item => { + switch (item.type) { + case String: + return ( +
+

{item.name}

+ item.action(e.target.value)} + /> +
+ ) + case Boolean: + return ( +
+
+

{item.name}

+

{item.description}

+
+ item.action(e.target.checked)} + /> +
+ ) + case 'Button': + return ( +
+ +
{item.description}
+
+ ) + case 'Text': + return ( +
+
+

{item.name}

+

{item.description}

+
+
+ ) + case 'Select': + return ( +
+
+

{item.name}

+

{item.description}

+
+ +
+ ) + default: + return
{item.name}
+ } + }) + } +
+
+ ) + }) + + } + + return ( +
+ {renderFields()} +
+ ); +} + +export default injectSheet(styles)(Setting); \ No newline at end of file diff --git a/src/App/components/Settings/Volume.js b/src/App/components/Settings/Volume.js index 08046d13..257c68b3 100644 --- a/src/App/components/Settings/Volume.js +++ b/src/App/components/Settings/Volume.js @@ -1,18 +1,24 @@ -import React, { useContext } from 'react'; +import React, { useContext, useEffect } from 'react'; +import { connect } from 'react-redux' import { AudioPlayerContext } from '../../../Providers/AudioPlayerProvider'; +import { updateVolume } from '../Settings/actions'; +import RangeSlider from '../common/RangeSlider'; const Volume = ({ value, onChange }) => { + useEffect(() => { + if (onChange) onChange(value); + }, [onChange, value]) const { setVolume } = useContext(AudioPlayerContext); const handleChange = (e) => { const value = e.target.value; - setVolume(value) - onChange(e) + setVolume(value); + updateVolume(value); } return ( -

Volume

- +
)}; -export default Volume; \ No newline at end of file +const mapStateTotProps = ({ settings }) => ({ value: settings.userPreferences.volume}) +export default connect(mapStateTotProps)(Volume); \ No newline at end of file diff --git a/src/App/components/Settings/actions.js b/src/App/components/Settings/actions.js index 5d2b84a6..e1f17a8a 100644 --- a/src/App/components/Settings/actions.js +++ b/src/App/components/Settings/actions.js @@ -16,3 +16,9 @@ export const setAutoBeat = payload => store.dispatch({type: 'AUTOBEAT', payload} export const setAutoImport = payload => store.dispatch({type: 'AUTOIMPORT', payload}); +export const setOsuSongsPath = payload => store.dispatch({type: 'SONGSPATH', payload}); + +export const setLastScan = payload => store.dispatch({type: 'LASTSCAN', payload}); + + + diff --git a/src/App/components/Settings/baseConf.js b/src/App/components/Settings/baseConf.js index 8b4bca61..3f566ddf 100644 --- a/src/App/components/Settings/baseConf.js +++ b/src/App/components/Settings/baseConf.js @@ -34,24 +34,28 @@ module.exports = { description: 'Create a multiplayer room with the specified name and send you an invite once the room is ready' }, { - command: 'search ', - description: 'Search for beatmaps and return the 5 first occurences' + command: 'search ', + description: 'Perform a search as on beatconnect’s website and return the 4 first ranked occurrences and a link to the other results on Beatconnect.' }, { - command: 'get ', + command: 'get ', description: 'Return the download link for the requested beatmapId' }, { - command: 'pp ', - description: 'Return the pp value for the requested beatmapId' + command: 'pp ', + description: 'Return the pp value for the requested beatmapId WIP' }, { command: 'beat', - description: 'From a multiplayer lobby previously created with createRoom, return the download link of the current beatmap' + description: 'Return the beatconnect download link of the current beatmap in a multiplayer match.' }, { command: 'infos', description: 'Print available commands' + }, + { + command: 'join ', + description: 'Request the bot to connect to the specified match (Bot must be added as match referee before with `!mp addref`).' } ] } \ No newline at end of file diff --git a/src/App/components/Settings/index.js b/src/App/components/Settings/index.js index d86b6776..61bc78aa 100644 --- a/src/App/components/Settings/index.js +++ b/src/App/components/Settings/index.js @@ -1,25 +1,107 @@ -import React, { useEffect } from 'react'; -import { connect } from 'react-redux' -import { remote } from 'electron'; -import Volume from './Volume' -import History from './History'; +import React, { useEffect, useState, cloneElement, useContext } from 'react'; +import { remote, ipcRenderer, shell } from 'electron'; +import { connect } from 'react-redux'; +import { setIrcUser, setIrcPass, setIRCIsBot, setOSUApiKey, setPrefix, setAutoBeat, setAutoImport, setOsuSongsPath, setLastScan } from './actions'; import ConfLoader from './ConfLoader'; -import { updateVolume } from './actions'; -import Configuration from './Configuration'; -import Theme from './Theme'; +import NavPanelItem from '../common/NavPanel/Item'; +import NavPanel from '../common/NavPanel'; +import Setting from './Setting'; +import { HistoryContext } from '../../../Providers/HistoryProvider'; +import { TasksContext } from '../../../Providers/TasksProvider'; const Settings = ({ userPreferences, theme }) => { + const history = useContext(HistoryContext) + const { add, tasks } = useContext(TasksContext) + const { irc, osuApi, prefix, autoImport, osuSongsPath, lastScan } = userPreferences; + const [selected, setSelected] = useState('Bot'); useEffect(() => { return ConfLoader.save }, []) + const osuPathSetup = () => { + const path = remote.dialog.showOpenDialog({ + properties: ['openDirectory'] + }); + if (path) setOsuSongsPath(path[0]) + } + + const scanOsuSongs = () => { + if (!osuSongsPath) return alert('You need to select your songs folder before') + add({ name: 'Scanning beatmaps folder', status: 'running', description: '', section: 'Settings' }) + ipcRenderer.send('osuSongsScan', osuSongsPath) // User osu folder path + ipcRenderer.on('osuSongsScanStatus', (e, args) => { + //const {status} = args + console.log('status', args) + add({ name: 'Scanning beatmaps folder', status: 'running', description: `${Math.round(args * 100)}%`, section: 'Settings' }) + }) + ipcRenderer.on('osuSongsScanResults', (e, args) => { + if (tasks['Scanning beatmaps folder']) tasks['Scanning beatmaps folder'].terminate('Finished') + //args = JSON.parse(args) + if (args.err) console.error(`Error while scannings song: ${args.err}`) + else { + history.set({ ...history.history, ...args }) + setLastScan({date: Date.now(), beatmaps: Object.keys(args).length}) + } + }) + } + + const settings = { + Bot: { + IRC: [ + { name: 'Special bot account', value: irc.isBotAccount, action: setIRCIsBot, type: Boolean, description: 'Disable flood protection (2.5sec cooldown per msg) only for granteed account' }, + { name: 'Bancho IRC credentials', description: `If you don't have them already clic here or go to osu.ppy.sh/p/irc`, action: () => shell.openItem('https://osu.ppy.sh/p/irc'), type: 'Text' }, + { name: 'Username', value: irc.username, action: setIrcUser, type: String }, + { name: 'Password', value: irc.password, action: setIrcPass, type: String, pass: true }, + ], + Misc: [ + { name: 'Osu api key', value: osuApi.key, action: setOSUApiKey, type: String, pass: true }, + { name: 'Bot prefix', value: prefix, action: setPrefix, type: String } + ] + }, + Downloads: { + History: [ + { name: 'Auto import maps', value: autoImport, action: setAutoImport, type: Boolean }, + // { name: 'Beatmaps import method', value: 'auto', action: null, options: ['auto', 'bulk', 'manual'], type: 'Select' }, + { name: 'Clear history', action: history.clear, type: 'Button' }, + ], + 'Beatmaps location': [ + { name: osuSongsPath || 'No songs folder selected', description: 'By selecting your osu songs folder enable the Bulk import and scan option', type: 'Text' }, + { name: 'Select your Osu! Songs folder', value: autoImport, action: osuPathSetup, type: 'Button' }, + { name: 'Osu! beatmaps scan', description: 'Scan your osu folder to import all your previously downloded beatmaps to your Beatconnect history', type: 'Text' }, + { name: osuSongsPath ? 'Scan Osu! songs' : 'Songs folder not selected', value: autoImport, action: scanOsuSongs, description: lastScan ? `${lastScan.beatmaps} beatmaps found - Last scan ${new Date(lastScan.date).toDateString()}` : '', type: 'Button' }, + ] + }, + Version: { + version: [ + { name: `Thanks for using Beatconnect! - ${remote.app.getVersion()}` } + ] + } + } + + const renderItems = () => { + return Object.keys(settings).map(setting => setSelected(setting)} + padding="10px 20px" + > + {setHeader => cloneElement(, { setHeaderContent: setHeader })} + + ) + }; + return ( -
- updateVolume(e.target.value)} /> - - - -
{`Beatconnect client v${remote.app.getVersion()}`}
+
+ + {renderItems()} +
); } diff --git a/src/App/components/Settings/reducer.js b/src/App/components/Settings/reducer.js index 6d4af9ae..9a195744 100644 --- a/src/App/components/Settings/reducer.js +++ b/src/App/components/Settings/reducer.js @@ -27,6 +27,15 @@ export default (settings = ConfLoader.conf, { type, payload }) => { case 'AUTOIMPORT': userPreferences = { ...userPreferences, autoImport: payload } return { ...settings, userPreferences: {...userPreferences} } + case 'SIDEPANELEXPENDED': + userPreferences = { ...userPreferences, sidePanelExpended: payload } + return { ...settings, userPreferences: {...userPreferences} } + case 'SONGSPATH': + userPreferences = { ...userPreferences, osuSongsPath: payload } + return { ...settings, userPreferences: {...userPreferences} } + case 'LASTSCAN': + userPreferences = { ...userPreferences, lastScan: payload } + return { ...settings, userPreferences: {...userPreferences} } default: return settings; } diff --git a/src/App/components/Start/index.js b/src/App/components/Start/index.js deleted file mode 100644 index fc4bc56c..00000000 --- a/src/App/components/Start/index.js +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useEffect, createRef } from 'react' -import start from '../../../Bot'; -import { ProgressCircle, Button } from 'react-desktop/windows'; -import { connect } from 'react-redux'; - -const Start = ({ connected, theme, irc, osuApi }) => { - const that = createRef(); - const notReady = (!osuApi || !irc.username || !irc.password) - - useEffect(() => { - that.current.parentNode.style.padding = 0; // Dirty way to custom react-desktop component - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - - return ( -
- - {connected ?

- {connected === 'connecting' ? - 'Connecting to Bancho via IRC..' - : 'Connected!'} -

: null - } - { - notReady ? -

- Warning: no irc credential or osu API key found. Please go to settings section -

: null - } -
- ) -}; - -const mapStateToProps = ({ settings }) => ({ irc: settings.userPreferences.irc, osuApi: settings.userPreferences.osuApi.key }); -export default connect(mapStateToProps)(Start); \ No newline at end of file diff --git a/src/App/components/common/Beatmap/Cover.js b/src/App/components/common/Beatmap/Cover.js index 6e673019..b0f1bebf 100644 --- a/src/App/components/common/Beatmap/Cover.js +++ b/src/App/components/common/Beatmap/Cover.js @@ -1,32 +1,38 @@ -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, memo } from 'react' -const Cover = ({ url, width, height }) => { +const Cover = ({ url, width, height, paddingBottom, noFade }) => { const [loaded, isLoaded] = useState(false); const cover = new Image() - cover.onload = () => isLoaded(true) + cover.onload = () => isLoaded(url) useEffect(() => { - isLoaded(false) - cover.setAttribute('src', url); + // isLoaded(false) + if (loaded !== url) + cover.setAttribute('src', url); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [url]); + }, [url, loaded]); const style = { - opacity: loaded ? 1 : 0, - filter: `blur(${loaded ? 0 + 'px' : 10 + 'px'})`, - transition: '1s all', + opacity: noFade ? 1 : loaded ? 1 : 0, + filter: noFade ? '' : `blur(${loaded ? 0 + 'px' : 10 + 'px'})`, + transition: '200ms all', width: width || '100%', height: height || null, - paddingBottom: '15%', + paddingBottom: paddingBottom ? '15%' : 0, // margin: 'auto 0 auto 10px', backgroundPosition: 'center center', backgroundRepeat: 'no-repeat', backgroundSize: 'cover', - backgroundImage: `url('${url}')` + backgroundImage: `url('${url}')`, + backgroundColor: 'rgba(255, 255, 255, 0.02)' } return (
); } -export default Cover; \ No newline at end of file +const areEqual = (prevProps, nextProps) => { + console.log(JSON.stringify(prevProps) === JSON.stringify(nextProps)) + return JSON.stringify(prevProps) === JSON.stringify(nextProps) +} +export default memo(Cover, areEqual); \ No newline at end of file diff --git a/src/App/components/common/Beatmap/index.js b/src/App/components/common/Beatmap/index.js index 80cc0211..c4bf5967 100644 --- a/src/App/components/common/Beatmap/index.js +++ b/src/App/components/common/Beatmap/index.js @@ -10,33 +10,33 @@ import Badge from '../Badge'; const reqImgAssets = require.context('../../../../assets/img', true, /\.png$/); -const Beatmap = ({ theme, beatmap, width }) => { - console.log(beatmap.id, 'updated') +const Beatmap = ({ theme, beatmap, width, noFade }) => { + // console.log('updated', beatmap) const getDownloadUrl = ({ id, unique_id }) => { return `https://beatconnect.io/b/${id}/${unique_id}` } const [brightness, setBrightness] = useState(0.95) const [isPlaying, setIsPLaying] = useState(false) const { beatmapset_id, id, title, artist, creator, version, beatconnectDlLink } = beatmap; - + const bpmFlash = useRef(null); const style = isPlaying ? { - width: width || '80%', + width: width || '', filter: `brightness(${brightness})`, transitionDuration: `${50}ms` } : { - width: width || '80%', - } + width: width || '', + } const modePillsStyle = (mode) => ({ - backgroundImage: `url(${reqImgAssets(`./${mode}.png`)})`, width: 20, height: 20, - margin: 'auto 0.2vw', + margin: '3px', backgroundSize: 'contain', filter: 'brightness(0.85)', - }) + content: `url(${reqImgAssets(`./${mode}.png`)})` + }); useEffect(() => { if (isPlaying) { @@ -45,12 +45,12 @@ const Beatmap = ({ theme, beatmap, width }) => { setTimeout(() => setBrightness(0.95), (60000 / beatmap.bpm) / 2.5) }, 60000 / beatmap.bpm) } - return () => bpmFlash ? clearInterval(bpmFlash) : undefined - // eslint-disable-next-line react-hooks/exhaustive-deps + return () => bpmFlash.current ? clearInterval(bpmFlash.current) : undefined + // eslint-disable-next-line react-hooks/exhaustive-deps }, [isPlaying]) useEffect(() => { - return () => bpmFlash ? clearInterval(bpmFlash) : undefined + return () => bpmFlash.current ? clearInterval(bpmFlash.current) : undefined }, []) return ( @@ -59,7 +59,7 @@ const Beatmap = ({ theme, beatmap, width }) => { beatmap ? - + {title} {artist} {version ? {`[${version || ''}]`} : null} @@ -73,12 +73,11 @@ const Beatmap = ({ theme, beatmap, width }) => { {renderIcons('Search', theme.style)}
- {/* hop */} -
- {beatmap.mode_std ?
: null} - {beatmap.mode_mania ?
: null} - {beatmap.mode_taiko ?
: null} - {beatmap.mode_ctb ?
: null} +
+ {beatmap.mode_std ? std : null} + {beatmap.mode_mania ? mania : null} + {beatmap.mode_taiko ? taiko : null} + {beatmap.mode_ctb ? ctb : null}
{beatmap.status ? : null}
@@ -91,10 +90,10 @@ const Beatmap = ({ theme, beatmap, width }) => { } const areEqual = (prevProps, nextProps) => { - if (prevProps.beatmap.beatmapset_id){ + if (prevProps.beatmap.beatmapset_id) { return prevProps.beatmap.beatmapset_id === nextProps.beatmap.beatmapset_id } - if (prevProps.beatmap.id){ + if (prevProps.beatmap.id) { return prevProps.beatmap.id === nextProps.beatmap.id } return false diff --git a/src/App/components/common/NavPanel/Header.js b/src/App/components/common/NavPanel/Header.js new file mode 100644 index 00000000..9363934a --- /dev/null +++ b/src/App/components/common/NavPanel/Header.js @@ -0,0 +1,38 @@ +import React from 'react'; +import injectSheet from 'react-jss'; + +const style = { + header: { + position: 'relative', + height: '48px', + display: 'flex', + alignItems: 'center', + // font-family: "Segoe UI", Frutiger, "Frutiger Linotype", "Dejavu Sans", "Helvetica Neue", Arial, sans-serif, + fontSize: '15px', + textTransform: 'uppercase', + padding: '0px 24px', + overflow: 'hidden', + cursor: 'default', + userSelect: 'none', + color: props => props.theme.dark ? '#fff' : '#000' + }, + divider: { + margin: '0 10px', + width: '1px', + height: '80%', + position: 'relative', + boxSizing: 'border-box', + backgroundColor: 'hsla(0,0%,100%,.1)', + } +}; +const Header = ({ title, classes, children }) => { + return ( +
+ {title} +
+ {children} +
+ ); +} + +export default injectSheet(style)(Header); \ No newline at end of file diff --git a/src/App/components/common/NavPanel/Item.js b/src/App/components/common/NavPanel/Item.js new file mode 100644 index 00000000..5254d93f --- /dev/null +++ b/src/App/components/common/NavPanel/Item.js @@ -0,0 +1,88 @@ +import React, { useState } from 'react' +import injectSheet from 'react-jss'; +import Header from './Header' + +const styles = { + contentContainer: { + position: 'relative', + flexGrow: 1, + flexShrink: 1, + display: 'flex', + }, + contentSubContainer: { + display: 'flex', + flex: '1 1 0%', + flexDirection: 'column', + }, + content: { + display: 'flex', + flex: '1 1 0%', + flexDirection: 'column', + padding: '0px', + background: props => props.background, + transition: 'background 0ms !important', + + textAlign: 'center', + fontSize: 'calc(10px + 2vmin)', + color: 'white', + backgroundColor: '#121212', + textRendering: 'optimizelegibility', + fontFamily: 'Open Sans, sans - serif', + height: 'calc(100vh - 79px)', + overflowY: 'auto', + + '&::-webkit-scrollbar': { + width: '8px' + }, + '&::-webkit-scrollbar-track': { + background: '#2a2a2a' + }, + '&::-webkit-scrollbar-thumb': { + background: '#00965f' + } + + } +}; + +const Item = ({ classes, color, icon, selected, title, dark, padding, children, background, onSelect, header, theme }) => { + //console.log(children) + const [headerContent, setHeaderContent] = useState(null); + // const lastSection = useRef(title); + // useLayoutEffect(() => { + // console.log('==============') + // console.log('useLayoutEffect', title, lastSection, headerContent, (lastSection.current !== title)) + // if (headerContent && lastSection.current !== title) { + // console.log('RAN') + // setHeaderContent(null); + // lastSection.current = null; + // } + // console.log('==============') + // return () => lastSection.current = title; + // }, [title, headerContent]); + + return ( +
+
+
+ { + header ? +
+ {headerContent} +
: null + } +
+
+ {children(setHeaderContent)} +
+
+
+ ); +} + +Item.defaultProps = { + title: '', + background: '#000', + padding: 0, +} + +export default injectSheet(styles)(Item); \ No newline at end of file diff --git a/src/App/components/common/NavPanel/SidePanel/Tab.js b/src/App/components/common/NavPanel/SidePanel/Tab.js new file mode 100644 index 00000000..b976f8b1 --- /dev/null +++ b/src/App/components/common/NavPanel/SidePanel/Tab.js @@ -0,0 +1,94 @@ +import React from 'react' +import injectSheet from 'react-jss'; + +const styles = { + a: { + display: 'flex', + alignItems: 'center', + height: '44px', + backgroundColor: 'transparent', + cursor: 'pointer', + '&:hover': { + backgroundColor: 'rgba(255,255,255,0.05)', + }, + '&:hover .indicator': { + height: props => props.selected ? '48px' : '24px' + }, + '&:hover .tooltiptext': { + visibility: props => props.expended ? 'hidden' : 'visible', + } + + }, + span: { + display: 'flex', + alignItems: 'center', + color: 'rgb(255, 255, 255)', + fontSize: '15px', + letterSpacing: '0.4pt', + padding: '0px 16px', + transition: 'transform 0.1s ease-in 0s', + userSelect: 'none', + }, + i: { + marginRight: '8px', + height: '44px', + display: 'flex', + alignItems: 'center', + + }, + indicator: { + position: 'absolute', + left: 0, + margin: 0, + height: props => props.selected ? '40px' : '0px', + width: '3px', + backgroundColor: props => props.theme.color, + }, + title: { + visibility: props => props.expended ? 'visible' : 'hidden' + }, + tooltiptext: { + visibility: 'hidden', + width: '120px', + backgroundColor: '#2a2a2a', + color: '#fff', + textAlign: 'center', + padding: '5px 0', + borderRadius: '6px', + position: 'absolute', + left: '105%', + zIndex: 1, + }, +}; + +const Tab = ({ classes, selected, icon, title, onSelect }) => { + return ( + + {title} + +
+ + {icon} + + + {title} + + + + ); +} + +export default injectSheet(styles)(Tab); \ No newline at end of file diff --git a/src/App/components/common/NavPanel/SidePanel/TasksControl.js b/src/App/components/common/NavPanel/SidePanel/TasksControl.js new file mode 100644 index 00000000..5ddfc582 --- /dev/null +++ b/src/App/components/common/NavPanel/SidePanel/TasksControl.js @@ -0,0 +1,161 @@ +import React, { useContext } from 'react' +import injectSheet from 'react-jss'; +import renderIcons from '../../../../utils/renderIcons'; +import { TasksContext } from '../../../../../Providers/TasksProvider'; +import store from '../../../../../store'; + +const styles = { + '@keyframes spin': { + from: { + transform: 'rotate(0deg)' + }, + to: { + transform: 'rotate(359deg)' + } + }, + a: { + margin: 'auto 0 0 0', + display: 'flex', + alignItems: 'center', + // height: '44px', + minHeight: '44px', + backgroundColor: 'transparent', + cursor: 'pointer', + '&:hover': { + backgroundColor: 'rgba(255,255,255,0.05)', + }, + ' &:active': { + backgroundColor: 'rgba(255,255,255,0.05)', + }, + '&:hover .indicator': { + height: props => props.selected ? '48px' : '24px' + }, + '&:active .indicator': { + height: props => props.selected ? '48px' : '24px' + }, + '&:hover .tooltiptext': { + visibility: props => props.expended ? 'none' : 'visible', + }, + '&:active .tooltiptext': { + visibility: props => props.expended ? 'none' : 'visible', + } + + }, + span: { + display: 'flex', + alignItems: 'center', + color: 'rgb(255, 255, 255)', + fontSize: '15px', + letterSpacing: '0.4pt', + padding: '0px 16px', + transition: 'transform 0.1s ease-in 0s', + userSelect: 'none', + }, + i: { + margin: 'auto 8px 0px 0px', + height: '44px', + display: 'flex', + alignItems: 'center', + '& svg': { + animation: 'spin 3000ms infinite linear' + } + }, + indicator: { + position: 'absolute', + left: 0, + bottom: 98, + margin: 0, + height: props => props.selected ? '40px' : '0px', + width: '3px', + backgroundColor: props => props.theme.color, + }, + title: { + visibility: props => props.expended ? 'visible' : 'hidden' + }, + tooltiptext: { + width: '120px', + backgroundColor: '#2a2a2a', + color: '#fff', + borderRadius: '6px', + position: 'absolute', + left: '100%', + zIndex: 1, + textAlign: 'left', + padding: '0 0.6rem', + visibility: props => props.pop ? 'visible' : 'hidden', + }, + divider: { + width: '80px', + height: '1px', + backgroundColor: 'rgba(255,255,255, 0.05)', + } + +}; + +const TasksControl = ({ classes, onSelect, theme }) => { + const { tasks } = useContext(TasksContext) + const tasksKeys = Object.keys(tasks); + const active = tasksKeys.length > 0 + const renderContent = () => { + if (active) return( +
+ {tasksKeys.map(task => ( +
store.dispatch({type: 'UPDATEACTIVESECTION', payload: tasks[task].section})}> +
+
{tasks[task].name}
+
+
{tasks[task].description}
+
{tasks[task].description1}
+
+
+
+ ))} + {/* {lastTask.name ? +
+
{lastTask.name}
+
+
Terminated
+
{lastTask.description}
+
+
: null + + } */} +
+ )} + return ( + + + {renderContent()} + + +
+ + {renderIcons('Loading', theme.style)} + + + {renderContent()} + + + + ); +} + +TasksControl.defaultProps = { + pop: false +} + +export default injectSheet(styles)(TasksControl); \ No newline at end of file diff --git a/src/App/components/common/NavPanel/SidePanel/VolumeControl.js b/src/App/components/common/NavPanel/SidePanel/VolumeControl.js new file mode 100644 index 00000000..cb76ea06 --- /dev/null +++ b/src/App/components/common/NavPanel/SidePanel/VolumeControl.js @@ -0,0 +1,115 @@ +import React, { useState } from 'react' +import injectSheet from 'react-jss'; +import renderIcons from '../../../../utils/renderIcons'; +import Volume from '../../../Settings/Volume'; + +const styles = { + a: { + margin: '0 0 0 0', + display: 'flex', + alignItems: 'center', + height: '44px', + backgroundColor: 'transparent', + cursor: 'pointer', + '&:hover': { + backgroundColor: 'rgba(255,255,255,0.05)', + }, + ' &:active': { + backgroundColor: 'rgba(255,255,255,0.05)', + }, + '&:hover .indicator': { + height: props => props.selected ? '48px' : '24px' + }, + '&:active .indicator': { + height: props => props.selected ? '48px' : '24px' + }, + '&:hover .tooltiptext': { + visibility: props => props.expended ? 'hidden' : 'visible', + }, + '&:active .tooltiptext': { + visibility: props => props.expended ? 'hidden' : 'visible', + } + + }, + span: { + display: 'flex', + alignItems: 'center', + color: 'rgb(255, 255, 255)', + fontSize: '15px', + letterSpacing: '0.4pt', + padding: '0px 16px', + transition: 'transform 0.1s ease-in 0s', + userSelect: 'none', + }, + i: { + marginRight: '8px', + height: '44px', + display: 'flex', + alignItems: 'center', + }, + indicator: { + position: 'absolute', + left: 0, + margin: 0, + height: props => props.selected ? '40px' : '0px', + width: '3px', + backgroundColor: props => props.theme.color, + }, + title: { + visibility: props => props.expended ? 'visible' : 'hidden' + }, + tooltiptext: { + visibility: 'hidden', + width: '120px', + backgroundColor: '#2a2a2a', + color: '#fff', + textAlign: 'center', + padding: '5px 0', + borderRadius: '6px', + position: 'absolute', + left: '100%', + zIndex: 1, + }, + +}; + +const VolumeControl = ({ classes, onSelect, theme }) => { + const [volumeValue, setVolumeValue] = useState() + const updateIcon = () => { + if (volumeValue < 1) return renderIcons('VolumeMute', theme.style) + if (volumeValue < 35) return renderIcons('VolumeLow', theme.style) + if (volumeValue < 75) return renderIcons('VolumeMid', theme.style) + return renderIcons('VolumeHigh', theme.style) + } + return ( + + + + + +
+ + {updateIcon()} + + + + + + + ); +} + +export default injectSheet(styles)(VolumeControl); \ No newline at end of file diff --git a/src/App/components/common/NavPanel/SidePanel/index.js b/src/App/components/common/NavPanel/SidePanel/index.js new file mode 100644 index 00000000..f61b5646 --- /dev/null +++ b/src/App/components/common/NavPanel/SidePanel/index.js @@ -0,0 +1,76 @@ +import React from 'react' +import injectSheet from 'react-jss'; +import Tab from './Tab'; +import VolumeControl from './VolumeControl'; +import TasksControl from './TasksControl'; + +const styles = { + SidePanel: { + cursor: 'default', + userSelect: 'none', + display: 'flex', + position: 'relative', + flexGrow: 0, + flexShrink: 0, + flexDirection: 'column', + overflow: 'visible', + width: props => props.expended ? props.panelExpandedLength : props.panelCompactedLength, + backgroundColor: props => props.background, + }, + head: { + height: 48 + }, + svg: { + position: 'absolute', + padding: '8px 10px', + top: '7px', + left: '4px', + width: '20px', + height: '20px', + cursor: 'pointer', + + }, +}; + +const SidePanel = ({ classes, color, dark, items, panelExpandedLength, panelCompactedLength, expended, expendable, volume, tasks, setExpended, theme, background }) => { + const itemTab = () => items.map((item, i) => { + if (items.length - i === 1) return ( + + { tasks ? : null } + { volume ? : null} + + + ) + return + }) + + return ( +
+ { + expendable ? + +
+ setExpended(!expended)} + > + + + + : null + } + {itemTab()} +
+ ); +} + +export default injectSheet(styles)(SidePanel); \ No newline at end of file diff --git a/src/App/components/common/NavPanel/index.js b/src/App/components/common/NavPanel/index.js new file mode 100644 index 00000000..cabd0d86 --- /dev/null +++ b/src/App/components/common/NavPanel/index.js @@ -0,0 +1,44 @@ +import React, { useState } from 'react'; +import injectSheet from 'react-jss'; +import SidePanel from './SidePanel'; + +const styles = { + NavPanel: { + display: 'flex', + flexWrap: 'nowrap', + position: 'relative', + flex: '1 1 0%', + } +}; + +const NavPanel = ({ classes, panelExpandedLength, panelCompactedLength, defaultIsPanelExpanded, expendable, volume, tasks, sidePanelBackground, dark, color, children, theme, onExpended }) => { + const [isExpended, setIsExpended] = useState(defaultIsPanelExpanded); + return ( +
+ { setIsExpended(expended); onExpended(expended) }} + defaultIsPanelExpanded={defaultIsPanelExpanded} + panelCompactedLength={panelCompactedLength} + panelExpandedLength={panelExpandedLength} + expendable={expendable} + background={sidePanelBackground} + volume={volume} + tasks={tasks} + /> + + {children.filter(child => child.props.selected)} + +
+ ); +} + +NavPanel.defaultProps = { + panelExpandedLength: 150, + panelCompactedLength: 48, + defaultIsPanelExpanded: false +} + +export default injectSheet(styles)(NavPanel); diff --git a/src/App/components/common/RangeSlider.js b/src/App/components/common/RangeSlider.js new file mode 100644 index 00000000..3bfda6e6 --- /dev/null +++ b/src/App/components/common/RangeSlider.js @@ -0,0 +1,45 @@ +import React from 'react' +import injectSheet from 'react-jss'; +import convertRange from '../../utils/convertRange'; + +const styles = { + slider: { + '-webkit-appearance': 'none', + width: props => props.width || '100%', + height: '10px', + borderRadius: '5px', + //background: '#d7dcdf', + outline: 'none', + padding: 0, + margin: 0, + background: props => `linear-gradient(90deg, ${props.theme.color} ${convertRange(props.value, props.min, props.max, 0, 100)}%, #3a3a3a ${convertRange(props.value, props.min, props.max, 0, 100)}%)`, + '&::-webkit-slider-thumb': { + + appearance: 'none', + width: '20px', + height: '20px', + borderRadius: '50%', + background: props => props.theme.secondary || '#2a2a2a', + cursor: 'pointer', + transition: 'background .15s ease-in-out', + + '&:hover': { + background: props => props.theme.color, + } + }, + '&:active::-webkit-slider-thumb': { + background: props => props.theme.color, + }, + '&:focus::-webkit-slider-thumb': { + //boxShadow: props => `0 0 0 3px #2a2a2a, 0 0 0 6px ${props.theme.color}`, + } + } +}; + +const RangeSlider = ({ classes, value, min, max, onChange }) => { + return ( + + ); +} + +export default injectSheet(styles)(RangeSlider); \ No newline at end of file diff --git a/src/App/components/common/Toggle.js b/src/App/components/common/Toggle.js new file mode 100644 index 00000000..3e5ec76c --- /dev/null +++ b/src/App/components/common/Toggle.js @@ -0,0 +1,48 @@ +import React from 'react'; +import injectSheet from 'react-jss'; + +const styles = { + Toggle: { + position: 'relative', + display: 'inline-block', + width: props => props.width || '40px', + height: props => props.height ||'20px', + borderRadius: '25px', + margin: props => props.margin, + // backgroundColor: '#989898', + backgroundColor: props => props.checked ? props.theme.color : props.background || '#2a2a2a', + opacity: props => props.disabled ? 0.5 : 1 + }, + input: { + display: 'none' + }, + div: { + position: 'absolute', + borderRadius: "50%", + backgroundColor: '#DFDFDF', + transition: '.1s ease', + width: '18px', + height: '18px', + top: '1px', + left: props => props.checked ? '50%' : '1px', + } +}; + +const Toggle = ({ classes, onChange, checked, disabled }) => { + return ( +