Browse Source

Everything seems to be done

surstrommed 2 years ago
parent
commit
47213a77f5

File diff suppressed because it is too large
+ 18555 - 44
package-lock.json


+ 3 - 0
package.json

@@ -15,6 +15,7 @@
     "cdbreact": "^1.2.1",
     "music-metadata-browser": "^2.5.3",
     "react": "^17.0.2",
+    "react-audio-spectrum": "^0.1.0",
     "react-bootstrap": "^2.1.0",
     "react-dom": "^17.0.2",
     "react-double-marquee": "^1.1.0",
@@ -28,6 +29,8 @@
     "redux-saga": "^1.1.3",
     "redux-thunk": "^2.4.1",
     "sass": "^1.45.2",
+    "tarang": "^1.1.2",
+    "wavesurfer.js": "^5.2.0",
     "web-vitals": "^2.1.2"
   },
   "scripts": {

BIN
public/favicon.ico


BIN
public/faviconReact.ico


+ 1 - 1
public/index.html

@@ -24,7 +24,7 @@
       work correctly both with client-side routing and a non-root public URL.
       Learn how to configure a non-root public URL by running `npm run build`.
     -->
-    <title>React App</title>
+    <title>Navy Web Player</title>
   </head>
   <body>
     <noscript>You need to enable JavaScript to run this app.</noscript>

+ 43 - 365
src/App.js

@@ -1,9 +1,13 @@
-import { Main } from "./components/Main";
-import { Header } from "./components/Header";
+import { Main } from "./components/Main/Main";
+import { Header } from "./components/Main/Header";
 import { Router } from "react-router-dom";
 import { Provider } from "react-redux";
 import { createStore, combineReducers, applyMiddleware } from "redux";
 import { createBrowserHistory } from "history";
+import { Sidebar } from "./components/Main/Sidebar";
+import "./App.scss";
+import createSagaMiddleware from "redux-saga";
+import { all } from "redux-saga/effects";
 import {
   promiseReducer,
   authReducer,
@@ -13,20 +17,7 @@ import {
   routeReducer,
   scrollTracksReducer,
 } from "./reducers/index";
-import { Sidebar } from "./components/Sidebar";
-import "./App.scss";
-import createSagaMiddleware from "redux-saga";
-import {
-  all,
-  takeEvery,
-  takeLatest,
-  put,
-  call,
-  select,
-} from "redux-saga/effects";
-import * as actions from "./actions";
-import { delay, gql } from "./helpers";
-import { CAudioController } from "./components/AudioController";
+import { CAudioController } from "./components/Audio/AudioController";
 import {
   audioLoadWatcher,
   audioPlayWatcher,
@@ -37,7 +28,15 @@ import {
   audioSetVolumeWatcher,
   audioSetCurrentTimeWatcher,
   audioSetSeekTimeTrackWatcher,
-} from "./components/AudioHandler";
+} from "./components/Audio/AudioHandler";
+import * as otherSaga from "./actions/sagas";
+import {
+  actionFindUserPlaylists,
+  actionAllTracks,
+  actionFullClearTracks,
+  actionAboutMe,
+} from "./actions/types";
+
 export const history = createBrowserHistory();
 
 const sagaMiddleware = createSagaMiddleware();
@@ -56,19 +55,6 @@ export const store = createStore(
 
 function* rootSaga() {
   yield all([
-    promiseWatcher(),
-    aboutAnotherUserWatcher(),
-    aboutMeWatcher(),
-    routeWatcher(),
-    loginWatcher(),
-    registerWatcher(),
-    findTracksWatcher(),
-    findUserTracksWatcher(),
-    setAvatarWatcher(),
-    setNicknameWatcher(),
-    setEmailWatcher(),
-    setNewPasswordWatcher(),
-    searchWatcher(),
     audioLoadWatcher(),
     audioSetDurationWatcher(),
     audioPlayWatcher(),
@@ -78,342 +64,41 @@ function* rootSaga() {
     audioSetCurrentTimeWatcher(),
     audioSetSeekTimeTrackWatcher(),
     audioSetVolumeWatcher(),
-    findPlaylistByOwnerWatcher(),
-    createPlaylistWatcher(),
-    findPlaylistTracksWatcher(),
-    loadTracksToPlaylistWatcher(),
-    uploadTracksToPlaylistWatcher(),
-    loadNewTracksWatcher(),
-    addSkipTracksWatcher(),
-    clearSkipTracksWatcher(),
+    otherSaga.promiseWatcher(),
+    otherSaga.aboutMeWatcher(),
+    otherSaga.routeWatcher(),
+    otherSaga.loginWatcher(),
+    otherSaga.registerWatcher(),
+    otherSaga.findTracksWatcher(),
+    otherSaga.findUserTracksWatcher(),
+    otherSaga.setAvatarWatcher(),
+    otherSaga.setNicknameWatcher(),
+    otherSaga.setEmailWatcher(),
+    otherSaga.setNewPasswordWatcher(),
+    otherSaga.searchWatcher(),
+    otherSaga.findPlaylistByOwnerWatcher(),
+    otherSaga.createPlaylistWatcher(),
+    otherSaga.findPlaylistTracksWatcher(),
+    otherSaga.loadTracksToPlaylistWatcher(),
+    otherSaga.uploadTracksToPlaylistWatcher(),
+    otherSaga.loadNewTracksWatcher(),
+    otherSaga.addSkipTracksWatcher(),
+    otherSaga.clearSkipTracksWatcher(),
   ]);
 }
 
 sagaMiddleware.run(rootSaga);
 
-function* promiseWorker(action) {
-  const { name, promise } = action;
-  yield put(actions.actionPending(name));
-  try {
-    let data = yield promise;
-    yield put(actions.actionResolved(name, data));
-    return data;
-  } catch (error) {
-    yield put(actions.actionRejected(name, error));
-  }
-}
-
-function* promiseWatcher() {
-  yield takeEvery("PROMISE_START", promiseWorker);
-}
-
-function* aboutMeWorker() {
-  let { auth } = yield select();
-  if (auth) {
-    let { id } = auth?.payload?.sub;
-    yield call(promiseWorker, actions.actionFindUser(id));
-  }
-}
-
-function* aboutMeWatcher() {
-  yield takeEvery("ABOUT_ME", aboutMeWorker);
-}
-
-function* aboutAnotherUserWorker({ id }) {
-  yield call(promiseWorker, actions.actionFindUser(id, "anotherUser"));
-}
-
-function* aboutAnotherUserWatcher() {
-  yield takeEvery("ABOUT_ANOTHER_USER", aboutAnotherUserWorker);
-}
-
 if (localStorage.authToken) {
-  store.dispatch(actions.actionFindUserPlaylists());
-  store.dispatch(actions.actionFullClearTracks());
-  store.dispatch(actions.actionAllTracks());
-  store.dispatch(actions.actionAboutMe());
-}
-
-const queries = {};
-
-function* routeWorker({ match }) {
-  console.log(match);
-  if (match.path in queries) {
-    const { name, query, variables } = queries[match.path](match);
-    yield call(
-      promiseWorker,
-      actions.actionPromise(name, gql(query, variables))
-    );
-  }
-}
-
-function* routeWatcher() {
-  yield takeEvery("ROUTE", routeWorker);
-}
-
-function* loginWorker({ login, password }) {
-  let token = yield call(promiseWorker, actions.actionLogin(login, password));
-  if (token) {
-    yield put(actions.actionAuthLogin(token));
-    yield put(actions.actionAboutMe());
-  }
-  window.location.reload();
-}
-
-function* loginWatcher() {
-  yield takeEvery("FULL_LOGIN", loginWorker);
-}
-
-function* loadNewTracksWorker({ newTracks }) {
-  yield put(actions.actionLoadNewTracks(newTracks));
-}
-
-function* loadNewTracksWatcher() {
-  yield takeEvery("FULL_ADD_TRACKS", loadNewTracksWorker);
-}
-
-function* addSkipTracksWorker({ skipTracks }) {
-  yield put(actions.actionSkipTracks(skipTracks));
-}
-
-function* addSkipTracksWatcher() {
-  yield takeEvery("FULL_ADD_SKIP", addSkipTracksWorker);
-}
-
-function* clearSkipTracksWorker() {
-  yield put(actions.actionClearTracks());
-}
-
-function* clearSkipTracksWatcher() {
-  yield takeEvery("FULL_CLEAR_SKIP", clearSkipTracksWorker);
-}
-
-function* findTracksWorker({ skip }) {
-  let newTracks = yield call(promiseWorker, actions.actionFindTracks(skip));
-  if (newTracks) {
-    yield put(actions.actionFullLoadNewTracks(newTracks));
-  }
-}
-
-function* findTracksWatcher() {
-  yield takeEvery("FIND_ALL_TRACKS", findTracksWorker);
-}
-
-function* registerWorker({ login, password }) {
-  yield call(promiseWorker, actions.actionRegister(login, password));
-  let token = yield call(promiseWorker, actions.actionLogin(login, password));
-  if (token) {
-    yield put(actions.actionAuthLogin(token));
-    let { auth } = yield select();
-    let nick = login;
-    if (nick.includes("@")) {
-      nick = nick.substring(0, nick.indexOf("@"));
-      if (nick.length > 8) {
-        nick = nick.substring(0, 8);
-      }
-    }
-    let { id } = auth?.payload?.sub;
-    yield call(promiseWorker, actions.actionUserUpdate({ _id: id, nick }));
-    yield put(actions.actionAboutMe());
-  }
-}
-
-function* registerWatcher() {
-  yield takeEvery("FULL_REGISTER", registerWorker);
-}
-
-function* setAvatarWorker({ file }) {
-  let { _id } = yield call(promiseWorker, actions.actionUploadFile(file));
-  let { auth } = yield select();
-  if (auth) {
-    let { id } = auth?.payload?.sub;
-    yield call(
-      promiseWorker,
-      actions.actionUserUpdate({ _id: id, avatar: { _id } })
-    );
-    yield put(actions.actionAboutMe());
-  }
+  store.dispatch(actionFindUserPlaylists());
+  store.dispatch(actionFullClearTracks());
+  store.dispatch(actionAllTracks());
+  store.dispatch(actionAboutMe());
 }
 
-function* setAvatarWatcher() {
-  yield takeEvery("SET_AVATAR", setAvatarWorker);
-}
-
-function* setNicknameWorker({ _id, nick }) {
-  yield call(promiseWorker, actions.actionUserUpdate({ _id, nick }));
-  yield put(actions.actionAboutMe());
-}
-
-function* setNicknameWatcher() {
-  yield takeEvery("SET_NICKNAME", setNicknameWorker);
-}
-
-function* setEmailWorker({ _id, login }) {
-  yield call(promiseWorker, actions.actionUserUpdate({ _id, login }));
-  yield put(actions.actionAboutMe());
-}
-
-function* setEmailWatcher() {
-  yield takeEvery("SET_EMAIL", setEmailWorker);
-}
-
-function* setNewPasswordWorker({ login, password, newPassword }) {
-  yield call(
-    promiseWorker,
-    actions.actionChangePassword(login, password, newPassword)
-  );
-  yield put(actions.actionAboutMe());
-}
-
-function* setNewPasswordWatcher() {
-  yield takeEvery("SET_NEW_PASSWORD", setNewPasswordWorker);
-}
-
-export function* searchWorker({ text }) {
-  yield put(actions.actionSearchResult({ payload: null }));
-  yield delay(2000);
-  let payload = yield gql(
-    `
-          query searchTracks($query: String){
-            TrackFind(query: $query) {
-              _id url originalFileName
-              id3 {
-                  title, artist
-              }
-              playlists {
-                  _id, name
-              }
-              owner {
-                _id login nick
-              }
-          }
-          }`,
-    {
-      query: JSON.stringify([
-        {
-          $or: [{ originalFileName: `/${text}/` }],
-        },
-        {
-          sort: [{ name: 1 }],
-        },
-      ]),
-    }
-  );
-  yield put(actions.actionSearchResult({ payload }));
-}
-
-function* searchWatcher() {
-  yield takeEvery("SEARCH", searchWorker);
-}
-
-function* findPlaylistByOwnerWorker() {
-  let { auth } = yield select();
-  if (auth) {
-    let { id } = auth?.payload?.sub;
-    yield call(promiseWorker, actions.actionUserPlaylists(id));
-  }
-}
-
-function* findPlaylistByOwnerWatcher() {
-  yield takeEvery("FIND_PLAYLISTS", findPlaylistByOwnerWorker);
-}
-
-function* createPlaylistWorker({ name }) {
-  let { auth } = yield select();
-  if (auth) {
-    yield call(promiseWorker, actions.actionCreatePlaylist(name));
-    yield put(actions.actionFindUserPlaylists());
-  }
-}
+export const { ...states } = store.getState();
 
-function* createPlaylistWatcher() {
-  yield takeEvery("CREATE_PLAYLIST", createPlaylistWorker);
-}
-
-function* findUserTracksWorker({ _id }) {
-  yield call(promiseWorker, actions.actionUserTracks(_id));
-}
-
-function* findUserTracksWatcher() {
-  yield takeEvery("FIND_USER_TRACKS", findUserTracksWorker);
-}
-
-function* findPlaylistTracksWorker({ _id }) {
-  yield call(promiseWorker, actions.actionFindPlaylistTracks(_id));
-}
-
-function* findPlaylistTracksWatcher() {
-  yield takeEvery("PLAYLIST_TRACKS", findPlaylistTracksWorker);
-}
-
-function* loadTracksToPlaylistWorker({ idPlaylist, array }) {
-  yield call(
-    promiseWorker,
-    actions.actionLoadTracksToPlaylist(idPlaylist, array)
-  );
-}
-
-function* loadTracksToPlaylistWatcher() {
-  yield takeEvery("LOAD_TRACKS_PLAYLIST", loadTracksToPlaylistWorker);
-}
-
-if (
-  localStorage.authToken &&
-  history.location.pathname.includes("/myplaylist")
-) {
-  let { auth } = store.getState();
-  if (auth) {
-    let currentPlaylistId = history.location.pathname.substring(
-      history.location.pathname.lastIndexOf("/") + 1
-    );
-    store.dispatch(actions.actionPlaylistTracks(currentPlaylistId));
-  }
-}
-
-const uploadedTracks = [];
-
-export function* uploadTracksToPlaylistWorker({ file }) {
-  const currentTracksPlaylist = [];
-  let resultArray = [];
-  let idPlaylist = history.location.pathname.substring(
-    history.location.pathname.lastIndexOf("/") + 1
-  );
-  let { promise } = yield select();
-  if (promise?.playlistTracks?.payload?.tracks) {
-    promise?.playlistTracks?.payload?.tracks.map((playlistTrack) =>
-      currentTracksPlaylist.push(playlistTrack)
-    );
-  }
-  if (file) {
-    let track = yield call(
-      promiseWorker,
-      actions.actionUploadFile(file, "track")
-    );
-    uploadedTracks.push(track);
-    let allUploadTracks = [...currentTracksPlaylist, ...uploadedTracks];
-    allUploadTracks.map((uploadedTrack) =>
-      resultArray.push({ _id: uploadedTrack?._id })
-    );
-    yield put(actions.actionTracksToPlaylist(idPlaylist, resultArray));
-  }
-  yield put(actions.actionAboutMe());
-}
-
-export function* uploadTracksToPlaylistWatcher() {
-  yield takeEvery("UPLOAD_TRACKS", uploadTracksToPlaylistWorker);
-}
-
-if (localStorage.authToken && history.location.pathname.includes("/profile")) {
-  let currentUserId = history.location.pathname.substring(
-    history.location.pathname.lastIndexOf("/") + 1
-  );
-  let { auth } = store.getState();
-  if (auth) {
-    currentUserId === auth?.payload?.sub?.id
-      ? store.dispatch(actions.actionAboutMe())
-      : store.dispatch(actions.actionAboutAnotherUser(currentUserId));
-  }
-}
+console.log(states);
 
 store.subscribe(() => console.log(store.getState()));
 
@@ -425,14 +110,7 @@ function App() {
           <Header />
           <Sidebar />
           <Main />
-          {localStorage.authToken ? (
-            <CAudioController
-              name="name"
-              currentTime="currentTime"
-              duration="duration"
-              volume="volume"
-            />
-          ) : null}
+          {states?.auth?.token ? <CAudioController /> : null}
         </div>
       </Provider>
     </Router>

+ 34 - 6
src/App.scss

@@ -2,6 +2,15 @@
 $mainColor: #0b5ed7;
 $secondaryColor: #212529;
 
+:root {
+  --flick-big-text-shadow: 0 0 2px #fff, 0 0 4px #fff, 0 0 8px #fff,
+    0 0 15px $mainColor, 0 0 30px $mainColor, 0 0 40px $mainColor,
+    0 0 70px $mainColor, 0 0 100px $mainColor;
+  --flick-small-text-shadow: 0 0 1px #fff, 0 0 2px #fff, 0 0 5px #fff,
+    0 0 11px $mainColor, 0 0 20px $mainColor, 0 0 30px $mainColor,
+    0 0 55px $mainColor, 0 0 80px $mainColor;
+}
+
 body {
   background-color: #34393d;
   color: white;
@@ -9,6 +18,28 @@ body {
   height: 150vh;
 }
 
+.Main {
+  .containerMain {
+    padding: 1rem;
+    display: block;
+    position: absolute;
+    left: 50%;
+    width: 50%;
+    transform: translate(-50%, -50%);
+    text-align: center;
+    border: solid 2px #fff;
+    border-radius: 0.8rem;
+    box-shadow: 0 0 0.1rem #fff, 0 0 0.2rem #fff, 0 0 0.3rem rgb(0, 140, 255),
+      0 0 0.8rem rgb(52, 50, 190), 0 0 1rem rgb(43, 52, 177),
+      inset 0 0 1.3rem rgb(45, 97, 240);
+  }
+  .neon-text {
+    font-size: 2rem;
+    margin: 0;
+    padding: 0;
+  }
+}
+
 .spoilerText {
   float: left;
 }
@@ -59,10 +90,6 @@ body {
   height: 40px;
 }
 
-.MainContent {
-  margin-left: 7%;
-}
-
 .player-container {
   z-index: 1;
   margin-bottom: -72px;
@@ -105,8 +132,9 @@ body {
   left: 0;
   bottom: 0;
   .SongName {
-    margin-left: 1vh;
-    width: 25%;
+    margin-left: 5vh;
+    width: 20%;
+    font-size: 17px;
   }
   .Buttons {
     width: 70%;

+ 1 - 148
src/actions/index.js

@@ -1,4 +1,5 @@
 import { backURL, gql } from "../helpers";
+import { actionPromise } from "./types";
 
 export const actionUserUpdate = ({ _id, login, password, nick, avatar }) =>
   actionPromise(
@@ -115,7 +116,6 @@ export const actionFindTracks = (skip) =>
       }
   `,
       {
-        // { q: "[{}]" }
         q: JSON.stringify([
           {},
           {
@@ -242,150 +242,3 @@ export const actionUploadFile = (file, type = "photo") => {
     }).then((res) => res.json())
   );
 };
-
-// ================================================
-
-export const actionPending = (name) => ({
-  type: "PROMISE",
-  status: "PENDING",
-  name,
-});
-
-export const actionResolved = (name, payload) => ({
-  type: "PROMISE",
-  status: "RESOLVED",
-  name,
-  payload,
-});
-
-export const actionRejected = (name, error) => ({
-  type: "PROMISE",
-  status: "REJECTED",
-  name,
-  error,
-});
-
-export const actionAuthLogin = (token) => ({ type: "AUTH_LOGIN", token });
-
-export const actionAuthLogout = () => ({ type: "AUTH_LOGOUT" });
-
-export const actionPromise = (name, promise) => ({
-  type: "PROMISE_START",
-  name,
-  promise,
-});
-
-export const actionAboutAnotherUser = (id) => ({
-  type: "ABOUT_ANOTHER_USER",
-  id,
-});
-
-export const actionAboutMe = () => ({
-  type: "ABOUT_ME",
-});
-
-export const actionFullLogin = (login, password) => ({
-  type: "FULL_LOGIN",
-  login,
-  password,
-});
-
-export const actionFullRegister = (login, password) => ({
-  type: "FULL_REGISTER",
-  login,
-  password,
-});
-
-export const actionSetAvatar = (file) => ({
-  type: "SET_AVATAR",
-  file,
-});
-
-export const actionSetNickname = ({ _id, nick }) => ({
-  type: "SET_NICKNAME",
-  _id,
-  nick,
-});
-
-export const actionSetEmail = ({ _id, login }) => ({
-  type: "SET_EMAIL",
-  _id,
-  login,
-});
-
-export const actionSetNewPassword = (login, password, newPassword) => ({
-  type: "SET_NEW_PASSWORD",
-  login,
-  password,
-  newPassword,
-});
-
-export const actionSearch = (text) => ({ type: "SEARCH", text });
-
-export const actionSearchResult = (payload) => ({
-  type: "SEARCH_RESULT",
-  payload,
-});
-
-export const actionSetSearch = (action) => ({ type: "SET_SEARCH", action });
-
-export const actionLoadNewTracks = (newTracks) => ({
-  type: "ADD_TRACKS",
-  newTracks,
-});
-
-export const actionFullLoadNewTracks = (newTracks) => ({
-  type: "FULL_ADD_TRACKS",
-  newTracks,
-});
-
-export const actionSkipTracks = (skipTracks) => ({
-  type: "ADD_SKIP",
-  skipTracks,
-});
-
-export const actionFullSkipTracks = (skipTracks) => ({
-  type: "FULL_ADD_SKIP",
-  skipTracks,
-});
-
-export const actionClearTracks = () => ({
-  type: "CLEAR_SKIP",
-});
-
-export const actionFullClearTracks = () => ({
-  type: "FULL_CLEAR_SKIP",
-});
-
-export const actionAllTracks = (skip) => ({
-  type: "FIND_ALL_TRACKS",
-  skip,
-});
-
-export const actionTracksUser = (_id) => ({
-  type: "FIND_USER_TRACKS",
-  _id,
-});
-
-export const actionFindUserPlaylists = () => ({ type: "FIND_PLAYLISTS" });
-
-export const actionCreateNewPlaylist = (name) => ({
-  type: "CREATE_PLAYLIST",
-  name,
-});
-
-export const actionPlaylistTracks = (_id) => ({
-  type: "PLAYLIST_TRACKS",
-  _id,
-});
-
-export const actionTracksToPlaylist = (idPlaylist, array) => ({
-  type: "LOAD_TRACKS_PLAYLIST",
-  idPlaylist,
-  array,
-});
-
-export const actionUploadTracks = (file) => ({
-  type: "UPLOAD_TRACKS",
-  file,
-});

+ 298 - 0
src/actions/sagas.js

@@ -0,0 +1,298 @@
+import { put, takeEvery, select, call } from "redux-saga/effects";
+import { gql, delay, queries } from "./../helpers/index";
+import {
+  actionAboutMe,
+  actionTracksToPlaylist,
+  actionFindUserPlaylists,
+  actionSearchResult,
+  actionAuthLogin,
+  actionFullLoadNewTracks,
+  actionClearTracks,
+  actionSkipTracks,
+  actionLoadNewTracks,
+  actionPromise,
+  actionPending,
+  actionRejected,
+  actionResolved,
+  actionPlaylistTracks,
+} from "./types";
+import {
+  actionUploadFile,
+  actionLoadTracksToPlaylist,
+  actionFindPlaylistTracks,
+  actionUserTracks,
+  actionCreatePlaylist,
+  actionUserPlaylists,
+  actionChangePassword,
+  actionUserUpdate,
+  actionLogin,
+  actionRegister,
+  actionFindTracks,
+  actionFindUser,
+} from "./index";
+
+function* promiseWorker(action) {
+  const { name, promise } = action;
+  yield put(actionPending(name));
+  try {
+    let data = yield promise;
+    yield put(actionResolved(name, data));
+    return data;
+  } catch (error) {
+    yield put(actionRejected(name, error));
+  }
+}
+
+export function* promiseWatcher() {
+  yield takeEvery("PROMISE_START", promiseWorker);
+}
+
+function* aboutMeWorker() {
+  let { auth } = yield select();
+  if (auth) {
+    let { id } = auth?.payload?.sub;
+    yield call(promiseWorker, actionFindUser(id));
+  }
+}
+
+export function* aboutMeWatcher() {
+  yield takeEvery("ABOUT_ME", aboutMeWorker);
+}
+
+function* routeWorker({ match }) {
+  console.log(match);
+  if (match.path in queries) {
+    const { name, query, variables } = queries[match.path](match);
+    yield call(promiseWorker, actionPromise(name, gql(query, variables)));
+  }
+}
+
+export function* routeWatcher() {
+  yield takeEvery("ROUTE", routeWorker);
+}
+
+function* loginWorker({ login, password }) {
+  let token = yield call(promiseWorker, actionLogin(login, password));
+  if (token) {
+    yield put(actionAuthLogin(token));
+    yield put(actionAboutMe());
+  }
+  window.location.reload();
+}
+
+export function* loginWatcher() {
+  yield takeEvery("FULL_LOGIN", loginWorker);
+}
+
+function* loadNewTracksWorker({ newTracks }) {
+  yield put(actionLoadNewTracks(newTracks));
+}
+
+export function* loadNewTracksWatcher() {
+  yield takeEvery("FULL_ADD_TRACKS", loadNewTracksWorker);
+}
+
+function* addSkipTracksWorker({ skipTracks }) {
+  yield put(actionSkipTracks(skipTracks));
+}
+
+export function* addSkipTracksWatcher() {
+  yield takeEvery("FULL_ADD_SKIP", addSkipTracksWorker);
+}
+
+function* clearSkipTracksWorker() {
+  yield put(actionClearTracks());
+}
+
+export function* clearSkipTracksWatcher() {
+  yield takeEvery("FULL_CLEAR_SKIP", clearSkipTracksWorker);
+}
+
+function* findTracksWorker({ skip }) {
+  let newTracks = yield call(promiseWorker, actionFindTracks(skip));
+  if (newTracks) {
+    yield put(actionFullLoadNewTracks(newTracks));
+  }
+}
+
+export function* findTracksWatcher() {
+  yield takeEvery("FIND_ALL_TRACKS", findTracksWorker);
+}
+
+function* registerWorker({ login, password }) {
+  yield call(promiseWorker, actionRegister(login, password));
+  let token = yield call(promiseWorker, actionLogin(login, password));
+  if (token) {
+    yield put(actionAuthLogin(token));
+    let { auth } = yield select();
+    let nick = login;
+    if (nick.includes("@")) {
+      nick = nick.substring(0, nick.indexOf("@"));
+      if (nick.length > 8) {
+        nick = nick.substring(0, 8);
+      }
+    }
+    let { id } = auth?.payload?.sub;
+    yield call(promiseWorker, actionUserUpdate({ _id: id, nick }));
+    yield put(actionAboutMe());
+  }
+}
+
+export function* registerWatcher() {
+  yield takeEvery("FULL_REGISTER", registerWorker);
+}
+
+function* setAvatarWorker({ file }) {
+  let { _id } = yield call(promiseWorker, actionUploadFile(file));
+  let { auth } = yield select();
+  if (auth) {
+    let { id } = auth?.payload?.sub;
+    yield call(promiseWorker, actionUserUpdate({ _id: id, avatar: { _id } }));
+    yield put(actionAboutMe());
+  }
+}
+
+export function* setAvatarWatcher() {
+  yield takeEvery("SET_AVATAR", setAvatarWorker);
+}
+
+function* setNicknameWorker({ _id, nick }) {
+  yield call(promiseWorker, actionUserUpdate({ _id, nick }));
+  yield put(actionAboutMe());
+}
+
+export function* setNicknameWatcher() {
+  yield takeEvery("SET_NICKNAME", setNicknameWorker);
+}
+
+function* setEmailWorker({ _id, login }) {
+  yield call(promiseWorker, actionUserUpdate({ _id, login }));
+  yield put(actionAboutMe());
+}
+
+export function* setEmailWatcher() {
+  yield takeEvery("SET_EMAIL", setEmailWorker);
+}
+
+function* setNewPasswordWorker({ login, password, newPassword }) {
+  yield call(promiseWorker, actionChangePassword(login, password, newPassword));
+  yield put(actionAboutMe());
+}
+
+export function* setNewPasswordWatcher() {
+  yield takeEvery("SET_NEW_PASSWORD", setNewPasswordWorker);
+}
+
+function* searchWorker({ text }) {
+  yield put(actionSearchResult({ payload: null }));
+  yield delay(2000);
+  let payload = yield gql(
+    `
+          query searchTracks($query: String){
+            TrackFind(query: $query) {
+              _id url originalFileName
+              id3 {
+                  title, artist
+              }
+              playlists {
+                  _id, name
+              }
+              owner {
+                _id login nick
+              }
+          }
+          }`,
+    {
+      query: JSON.stringify([
+        {
+          $or: [{ originalFileName: `/${text}/` }],
+        },
+        {
+          sort: [{ name: 1 }],
+        },
+      ]),
+    }
+  );
+  yield put(actionSearchResult({ payload }));
+}
+
+export function* searchWatcher() {
+  yield takeEvery("SEARCH", searchWorker);
+}
+
+function* findPlaylistByOwnerWorker() {
+  let { auth } = yield select();
+  if (auth) {
+    let { id } = auth?.payload?.sub;
+    yield call(promiseWorker, actionUserPlaylists(id));
+  }
+}
+
+export function* findPlaylistByOwnerWatcher() {
+  yield takeEvery("FIND_PLAYLISTS", findPlaylistByOwnerWorker);
+}
+
+function* createPlaylistWorker({ name }) {
+  let { auth } = yield select();
+  if (auth) {
+    yield call(promiseWorker, actionCreatePlaylist(name));
+    yield put(actionFindUserPlaylists());
+  }
+}
+
+export function* createPlaylistWatcher() {
+  yield takeEvery("CREATE_PLAYLIST", createPlaylistWorker);
+}
+
+function* findUserTracksWorker({ _id }) {
+  yield call(promiseWorker, actionUserTracks(_id));
+}
+
+export function* findUserTracksWatcher() {
+  yield takeEvery("FIND_USER_TRACKS", findUserTracksWorker);
+}
+
+function* findPlaylistTracksWorker({ _id }) {
+  yield call(promiseWorker, actionFindPlaylistTracks(_id));
+}
+
+export function* findPlaylistTracksWatcher() {
+  yield takeEvery("PLAYLIST_TRACKS", findPlaylistTracksWorker);
+}
+
+function* loadTracksToPlaylistWorker({ idPlaylist, array }) {
+  yield call(promiseWorker, actionLoadTracksToPlaylist(idPlaylist, array));
+  yield put(actionPlaylistTracks(idPlaylist));
+}
+
+export function* loadTracksToPlaylistWatcher() {
+  yield takeEvery("LOAD_TRACKS_PLAYLIST", loadTracksToPlaylistWorker);
+}
+
+const uploadedTracks = [];
+
+function* uploadTracksToPlaylistWorker({ file }) {
+  const currentTracksPlaylist = [];
+  let resultArray = [];
+  let { route } = yield select();
+  let idPlaylist = route?.params?._id;
+  let { promise } = yield select();
+  if (promise?.playlistTracks?.payload?.tracks) {
+    promise?.playlistTracks?.payload?.tracks.map((playlistTrack) =>
+      currentTracksPlaylist.push(playlistTrack)
+    );
+  }
+  if (file) {
+    let track = yield call(promiseWorker, actionUploadFile(file, "track"));
+    uploadedTracks.push(track);
+    let allUploadTracks = [...currentTracksPlaylist, ...uploadedTracks];
+    allUploadTracks.map((uploadedTrack) =>
+      resultArray.push({ _id: uploadedTrack?._id })
+    );
+    yield put(actionTracksToPlaylist(idPlaylist, resultArray));
+  }
+}
+
+export function* uploadTracksToPlaylistWatcher() {
+  yield takeEvery("UPLOAD_TRACKS", uploadTracksToPlaylistWorker);
+}

+ 223 - 0
src/actions/types.js

@@ -0,0 +1,223 @@
+export const actionLoadAudio = (track, playlist, indexInPlaylist) => ({
+  type: "LOAD_TRACK",
+  track,
+  playlist,
+  indexInPlaylist,
+});
+
+export const actionFullLoadAudio = (track, playlist, indexInPlaylist) => ({
+  type: "FULL_LOAD_TRACK",
+  track,
+  playlist,
+  indexInPlaylist,
+});
+
+export const actionPlayAudio = ({ isPlaying }) => ({
+  type: "PLAY_TRACK",
+  isPlaying,
+});
+
+export const actionFullPlayAudio = (isPlaying) => ({
+  type: "FULL_PLAY_TRACK",
+  isPlaying,
+});
+
+export const actionPauseAudio = ({ isPaused }) => ({
+  type: "PAUSE_TRACK",
+  isPaused,
+});
+
+export const actionFullPauseAudio = (isPaused) => ({
+  type: "FULL_PAUSE_TRACK",
+  isPaused,
+});
+
+export const actionPrevTrack = (indexInPlaylist) => ({
+  type: "PREV_TRACK",
+  indexInPlaylist,
+});
+
+export const actionNextTrack = (indexInPlaylist) => ({
+  type: "NEXT_TRACK",
+  indexInPlaylist,
+});
+
+export const actionSetCurrentTimeTrack = (currentTime) => ({
+  type: "SET_CURRENT_TIME_TRACK",
+  currentTime,
+});
+
+export const actionFullSetCurrentTimeTrack = (currentTime) => ({
+  type: "FULL_SET_CURRENT_TIME_TRACK",
+  currentTime,
+});
+
+export const actionSetSeekTimeTrack = (seekTime) => ({
+  type: "SET_SEEK_TIME_TRACK",
+  currentTime: seekTime,
+});
+
+export const actionFullSetSeekTimeTrack = (seekTime) => ({
+  type: "FULL_SET_SEEK_TIME_TRACK",
+  seekTime,
+});
+
+export const actionSetVolume = (volume) => ({
+  type: "SET_VOLUME",
+  volume,
+});
+
+export const actionFullSetVolume = (volume) => ({
+  type: "FULL_SET_VOLUME",
+  volume,
+});
+
+export const actionSetDuration = (duration) => ({
+  type: "SET_DURATION",
+  duration,
+});
+
+export const actionFullSetDuration = (duration) => ({
+  type: "FULL_SET_DURATION",
+  duration,
+});
+
+export const actionPending = (name) => ({
+  type: "PROMISE",
+  status: "PENDING",
+  name,
+});
+
+export const actionResolved = (name, payload) => ({
+  type: "PROMISE",
+  status: "RESOLVED",
+  name,
+  payload,
+});
+
+export const actionRejected = (name, error) => ({
+  type: "PROMISE",
+  status: "REJECTED",
+  name,
+  error,
+});
+
+export const actionAuthLogin = (token) => ({ type: "AUTH_LOGIN", token });
+
+export const actionAuthLogout = () => ({ type: "AUTH_LOGOUT" });
+
+export const actionPromise = (name, promise) => ({
+  type: "PROMISE_START",
+  name,
+  promise,
+});
+
+export const actionAboutMe = () => ({
+  type: "ABOUT_ME",
+});
+
+export const actionFullLogin = (login, password) => ({
+  type: "FULL_LOGIN",
+  login,
+  password,
+});
+
+export const actionFullRegister = (login, password) => ({
+  type: "FULL_REGISTER",
+  login,
+  password,
+});
+
+export const actionSetAvatar = (file) => ({
+  type: "SET_AVATAR",
+  file,
+});
+
+export const actionSetNickname = ({ _id, nick }) => ({
+  type: "SET_NICKNAME",
+  _id,
+  nick,
+});
+
+export const actionSetEmail = ({ _id, login }) => ({
+  type: "SET_EMAIL",
+  _id,
+  login,
+});
+
+export const actionSetNewPassword = (login, password, newPassword) => ({
+  type: "SET_NEW_PASSWORD",
+  login,
+  password,
+  newPassword,
+});
+
+export const actionSearch = (text) => ({ type: "SEARCH", text });
+
+export const actionSearchResult = (payload) => ({
+  type: "SEARCH_RESULT",
+  payload,
+});
+
+export const actionSetSearch = (action) => ({ type: "SET_SEARCH", action });
+
+export const actionLoadNewTracks = (newTracks) => ({
+  type: "ADD_TRACKS",
+  newTracks,
+});
+
+export const actionFullLoadNewTracks = (newTracks) => ({
+  type: "FULL_ADD_TRACKS",
+  newTracks,
+});
+
+export const actionSkipTracks = (skipTracks) => ({
+  type: "ADD_SKIP",
+  skipTracks,
+});
+
+export const actionFullSkipTracks = (skipTracks) => ({
+  type: "FULL_ADD_SKIP",
+  skipTracks,
+});
+
+export const actionClearTracks = () => ({
+  type: "CLEAR_SKIP",
+});
+
+export const actionFullClearTracks = () => ({
+  type: "FULL_CLEAR_SKIP",
+});
+
+export const actionAllTracks = (skip) => ({
+  type: "FIND_ALL_TRACKS",
+  skip,
+});
+
+export const actionTracksUser = (_id) => ({
+  type: "FIND_USER_TRACKS",
+  _id,
+});
+
+export const actionFindUserPlaylists = () => ({ type: "FIND_PLAYLISTS" });
+
+export const actionCreateNewPlaylist = (name) => ({
+  type: "CREATE_PLAYLIST",
+  name,
+});
+
+export const actionPlaylistTracks = (_id) => ({
+  type: "PLAYLIST_TRACKS",
+  _id,
+});
+
+export const actionTracksToPlaylist = (idPlaylist, array) => ({
+  type: "LOAD_TRACKS_PLAYLIST",
+  idPlaylist,
+  array,
+});
+
+export const actionUploadTracks = (file) => ({
+  type: "UPLOAD_TRACKS",
+  file,
+});

+ 20 - 6
src/components/Audio.js

@@ -1,9 +1,8 @@
 import { Button } from "react-bootstrap";
 import { connect } from "react-redux";
-import { backURL } from "../helpers/index";
+import { backURL } from "../../helpers/index";
 import { Link } from "react-router-dom";
-import * as actions from "./AudioHandler";
-import { skipValue } from "./../helpers/index";
+import * as actions from "./../../actions/types";
 
 const AudioTrack = ({
   personal,
@@ -31,7 +30,7 @@ const AudioTrack = ({
           {route.url === "/search"
             ? index + 1 + loadedTracks?.skipTracks
             : index + 1}{" "}
-          |
+          |{" "}
           <span>
             {track?.originalFileName
               ? truncText(track?.originalFileName)
@@ -39,7 +38,16 @@ const AudioTrack = ({
           </span>
         </span>
       </div>
-      <Button onClick={() => loadAudio(track, playlist, index)}>
+      <Button
+        variant={`${
+          track._id === player?.track?._id && player?.isPlaying
+            ? "success"
+            : track._id === player?.track?._id && player?.isPaused
+            ? "danger"
+            : "primary"
+        }`}
+        onClick={() => loadAudio(track, playlist, index)}
+      >
         <i
           className={`fas ${
             track._id === player?.track?._id && player?.isPlaying
@@ -49,7 +57,13 @@ const AudioTrack = ({
         ></i>
       </Button>
       <a
-        className="btn btn-primary h-50 ml-1 my-auto"
+        className={`btn btn-${
+          track._id === player?.track?._id && player?.isPlaying
+            ? "success"
+            : track._id === player?.track?._id && player?.isPaused
+            ? "danger"
+            : "primary"
+        } h-50 ml-1 my-auto`}
         href={`${backURL}/${track?.url}`}
         role="button"
       >

+ 1 - 3
src/components/AudioController.js

@@ -1,7 +1,6 @@
 import { Button } from "react-bootstrap";
 import { connect } from "react-redux";
-import { useState } from "react";
-import * as actions from "./AudioHandler";
+import * as actions from "./../../actions/types";
 
 const AudioController = ({
   player,
@@ -25,7 +24,6 @@ const AudioController = ({
       ? "00:00"
       : `${minutes}:${formatSeconds}`;
   }
-
   return (
     <div className="AudioController">
       <span className="SongName">

+ 22 - 86
src/components/AudioHandler.js

@@ -1,6 +1,21 @@
 import { takeEvery, put, select } from "redux-saga/effects";
-import { store } from "../App";
-import { backURL } from "./../helpers/index";
+import { store } from "../../App";
+import { backURL } from "../../helpers/index";
+import {
+  actionLoadAudio,
+  actionFullPlayAudio,
+  actionFullPauseAudio,
+  actionFullSetDuration,
+  actionFullSetCurrentTimeTrack,
+  actionNextTrack,
+  actionPlayAudio,
+  actionPauseAudio,
+  actionFullLoadAudio,
+  actionSetCurrentTimeTrack,
+  actionSetSeekTimeTrack,
+  actionSetVolume,
+  actionSetDuration,
+} from "./../../actions/types";
 
 const audio = new Audio();
 
@@ -40,9 +55,14 @@ export function* audioLoadWatcher() {
 function* audioPlayWorker(isPlaying) {
   console.log("Play track");
   audio.play();
+  let { player } = yield select();
+  audio.volume = player?.volume;
   audio.ontimeupdate = (e) => {
     store.dispatch(actionFullSetCurrentTimeTrack(e.path[0].currentTime));
   };
+  audio.onended = () => {
+    store.dispatch(actionNextTrack(player?.indexInPlaylist));
+  };
   yield put(actionPlayAudio(isPlaying));
 }
 
@@ -144,87 +164,3 @@ function* audioSetDurationWorker({ duration }) {
 export function* audioSetDurationWatcher() {
   yield takeEvery("FULL_SET_DURATION", audioSetDurationWorker);
 }
-
-export const actionLoadAudio = (track, playlist, indexInPlaylist) => ({
-  type: "LOAD_TRACK",
-  track,
-  playlist,
-  indexInPlaylist,
-});
-
-export const actionFullLoadAudio = (track, playlist, indexInPlaylist) => ({
-  type: "FULL_LOAD_TRACK",
-  track,
-  playlist,
-  indexInPlaylist,
-});
-
-export const actionPlayAudio = ({ isPlaying }) => ({
-  type: "PLAY_TRACK",
-  isPlaying,
-});
-
-export const actionFullPlayAudio = (isPlaying) => ({
-  type: "FULL_PLAY_TRACK",
-  isPlaying,
-});
-
-export const actionPauseAudio = ({ isPaused }) => ({
-  type: "PAUSE_TRACK",
-  isPaused,
-});
-
-export const actionFullPauseAudio = (isPaused) => ({
-  type: "FULL_PAUSE_TRACK",
-  isPaused,
-});
-
-export const actionPrevTrack = (indexInPlaylist) => ({
-  type: "PREV_TRACK",
-  indexInPlaylist,
-});
-
-export const actionNextTrack = (indexInPlaylist) => ({
-  type: "NEXT_TRACK",
-  indexInPlaylist,
-});
-
-export const actionSetCurrentTimeTrack = (currentTime) => ({
-  type: "SET_CURRENT_TIME_TRACK",
-  currentTime,
-});
-
-export const actionFullSetCurrentTimeTrack = (currentTime) => ({
-  type: "FULL_SET_CURRENT_TIME_TRACK",
-  currentTime,
-});
-
-export const actionSetSeekTimeTrack = (seekTime) => ({
-  type: "SET_SEEK_TIME_TRACK",
-  currentTime: seekTime,
-});
-
-export const actionFullSetSeekTimeTrack = (seekTime) => ({
-  type: "FULL_SET_SEEK_TIME_TRACK",
-  seekTime,
-});
-
-export const actionSetVolume = (volume) => ({
-  type: "SET_VOLUME",
-  volume,
-});
-
-export const actionFullSetVolume = (volume) => ({
-  type: "FULL_SET_VOLUME",
-  volume,
-});
-
-export const actionSetDuration = (duration) => ({
-  type: "SET_DURATION",
-  duration,
-});
-
-export const actionFullSetDuration = (duration) => ({
-  type: "FULL_SET_DURATION",
-  duration,
-});

src/components/AuthCheck.js → src/components/Auth/AuthCheck.js


+ 6 - 10
src/pages/Auth.js

@@ -1,16 +1,12 @@
-import { actionAuthLogout } from "../actions/index";
 import { NavDropdown } from "react-bootstrap";
 import { Link } from "react-router-dom";
 import { connect } from "react-redux";
-import { history, store } from "../App";
-import { backURL } from "../helpers";
+import { history, store } from "../../App";
+import { backURL } from "../../helpers";
+import { actionAuthLogout } from "../../actions/types";
 
-const Auth = ({ auth, promise, actionLogOut }) => {
-  if (
-    auth?.token &&
-    (history.location.pathname === "/login" ||
-      history.location.pathname === "/signup")
-  ) {
+const Auth = ({ auth, promise, route, actionLogOut }) => {
+  if (auth?.token && (route?.url === "/login" || route?.url === "/signup")) {
     history.push("/");
   }
   let id;
@@ -66,7 +62,7 @@ const Auth = ({ auth, promise, actionLogOut }) => {
 };
 
 export const CAuth = connect(
-  (state) => ({ auth: state.auth, promise: state.promise }),
+  (state) => ({ route: state.route, auth: state.auth, promise: state.promise }),
   {
     actionLogOut: actionAuthLogout,
   }

+ 0 - 39
src/components/Dropzone.js

@@ -1,39 +0,0 @@
-import React, { useCallback } from "react";
-import { useDropzone } from "react-dropzone";
-import { connect } from "react-redux";
-import { actionSetAvatar, actionUploadTracks } from "../actions";
-
-const MyDropzone = ({ promise, onloadAvatar, onloadMusic }) => {
-  const onDrop = useCallback(
-    (acceptedFiles) => {
-      if (acceptedFiles[0].type.includes("audio")) {
-        for (let i = 0; i < acceptedFiles.length; i++) {
-          onloadMusic(acceptedFiles[i]);
-        }
-      } else {
-        onloadAvatar(acceptedFiles[0]);
-      }
-    },
-    [onloadAvatar, onloadMusic]
-  );
-  const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
-
-  return (
-    <div className="mt-2 text-center customBorder mx-auto" {...getRootProps()}>
-      <input {...getInputProps()} />
-      {isDragActive ? (
-        <p>Поместите файлы сюда...</p>
-      ) : (
-        <p>
-          Для загрузки перетащите файлы сюда или нажмите на поле и выберите
-          файлы
-        </p>
-      )}
-    </div>
-  );
-};
-
-export const CMyDropzone = connect((state) => ({ promise: state.promise }), {
-  onloadAvatar: actionSetAvatar,
-  onloadMusic: actionUploadTracks,
-})(MyDropzone);

+ 2 - 2
src/components/Header.js

@@ -2,8 +2,8 @@ import React from "react";
 import { Navbar, Container, Nav, Button } from "react-bootstrap";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faArrowLeft, faArrowRight } from "@fortawesome/free-solid-svg-icons";
-import { CAuth } from "../pages/Auth";
-import { history } from "./../App";
+import { CAuth } from "../Auth/AuthHeader";
+import { history } from "../../App";
 
 export const Header = () => {
   return (

+ 15 - 12
src/components/Main.js

@@ -1,20 +1,23 @@
 import React from "react";
-import { Route, Switch, withRouter } from "react-router-dom";
-import { CLoginForm } from "../pages/Login";
-import { CSignUpForm } from "../pages/Register";
-import { Page404 } from "../pages/Page404";
-import { CSearch } from "./../pages/Search";
-import { CLibrary } from "./../pages/Library";
-import { CProfile } from "./../pages/Profile";
-import { MyPlaylistTracks } from "./Playlist";
-import { CProtectedRoute, CRRoute } from "./RRoute";
+import { Switch, withRouter } from "react-router-dom";
+import { CProtectedRoute, CRRoute } from "./../Other/RRoute";
+import { CProfile } from "./../../pages/Profile";
+import { CLibrary } from "./../../pages/Library";
+import { CSearch } from "./../../pages/Search";
+import { Page404 } from "./../../pages/Page404";
+import { CSignUpForm } from "./../../pages/Register";
+import { CLoginForm } from "./../../pages/Login";
+import { MyPlaylistTracks } from "./../Playlist/Playlist";
+import gif from "../../images/gifka.gif";
 
 const Content = ({ children }) => <div className="Content">{children}</div>;
-
 const PageMain = () => {
   return (
-    <div className="MainContent">
-      <h1 className="text-center">Главная страница</h1>
+    <div className="Main text-center">
+      <div class="containerMain mt-5">
+        <h1 className="neon-text">Добро пожаловать в Navy Web Player!</h1>
+      </div>
+      <img src={gif} alt="background" style={{ width: "30%", height: "30%" }} />
     </div>
   );
 };

+ 1 - 10
src/components/Sidebar.js

@@ -6,7 +6,7 @@ import {
   CDBSidebarMenuItem,
 } from "cdbreact";
 import { NavLink } from "react-router-dom";
-import { Logo } from "./Logo";
+import { Logo } from "../Other/Logo";
 
 export const Sidebar = () => {
   return (
@@ -40,12 +40,3 @@ export const Sidebar = () => {
     </div>
   );
 };
-
-// <CDBSidebarFooter
-// className="m-1"
-// style={{
-//   textAlign: "center",
-// }}
-// >
-// Navy Web Player
-// </CDBSidebarFooter>

+ 57 - 0
src/components/Other/Dropzone.js

@@ -0,0 +1,57 @@
+import React, { useCallback, useState } from "react";
+import { useDropzone } from "react-dropzone";
+import { connect } from "react-redux";
+import { actionSetAvatar, actionUploadTracks } from "../../actions/types";
+
+const MyDropzone = ({ promise, onloadAvatar, onloadMusic }) => {
+  const [error, setError] = useState(false);
+  const onDrop = useCallback(
+    (acceptedFiles) => {
+      if (acceptedFiles[0].type.includes("audio")) {
+        if (
+          acceptedFiles.length +
+            (promise?.playlistTracks?.payload?.tracks
+              ? promise?.playlistTracks?.payload?.tracks?.length
+              : 0) <
+          100
+        ) {
+          for (let i = 0; i < acceptedFiles.length; i++) {
+            onloadMusic(acceptedFiles[i]);
+          }
+        } else {
+          setError(true);
+        }
+      } else {
+        onloadAvatar(acceptedFiles[0]);
+      }
+    },
+    [onloadAvatar, onloadMusic]
+  );
+  const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
+
+  return (
+    <div
+      className={`${
+        error ? "text-danger" : null
+      } mt-2 text-center customBorder mx-auto`}
+      {...getRootProps()}
+    >
+      <input {...getInputProps()} />
+      {isDragActive ? setError(false) : null}
+      {isDragActive ? (
+        <p>Поместите файлы сюда...</p>
+      ) : (
+        <p>
+          {error
+            ? "Ошибка! Максимально допустимое количество треков в плейлисте - 100 штук."
+            : "Для загрузки перетащите файлы сюда или нажмите на поле и выберите их локально."}
+        </p>
+      )}
+    </div>
+  );
+};
+
+export const CMyDropzone = connect((state) => ({ promise: state.promise }), {
+  onloadAvatar: actionSetAvatar,
+  onloadMusic: actionUploadTracks,
+})(MyDropzone);

src/components/Loader.js → src/components/Other/Loader.js


src/components/Logo.js → src/components/Other/Logo.js


src/components/Preloader.js → src/components/Other/Preloader.js


+ 2 - 2
src/components/RRoute.js

@@ -1,6 +1,6 @@
 import { connect } from "react-redux";
-import { Route, Redirect } from "react-router-dom";
-import { AuthCheck } from "./AuthCheck";
+import { Route } from "react-router-dom";
+import { AuthCheck } from "../Auth/AuthCheck";
 
 const RRoute = ({ action, component: Component, ...routeProps }) => {
   const WrapperComponent = (componentProps) => {

src/components/Spoiler.js → src/components/Other/Spoiler.js


src/components/PlayerHeader.js → src/components/Playlist/PlayerHeader.js


+ 29 - 19
src/components/Playlist.js

@@ -1,19 +1,18 @@
 import { useState } from "react";
 import { connect } from "react-redux";
 import { Link } from "react-router-dom";
-import { CMyDropzone } from "./Dropzone";
-import {
-  actionCreateNewPlaylist,
-  actionTracksToPlaylist,
-} from "./../actions/index";
 import { SortableContainer, SortableElement } from "react-sortable-hoc";
 import { arrayMoveImmutable } from "array-move";
-import { CAudio } from "./Audio";
-import { history } from "./../App";
-import { CPreloader } from "./Preloader";
+import { CAudio } from "./../Audio/Audio";
 import { PlayerHeader } from "./PlayerHeader";
+import { CPreloader } from "./../Other/Preloader";
+import { CMyDropzone } from "./../Other/Dropzone";
+import {
+  actionCreateNewPlaylist,
+  actionTracksToPlaylist,
+} from "../../actions/types";
 
-const PlaylistTrackItem = ({ track, index, key, playlist }) => {
+const PlaylistTrackItem = ({ track, key, playlist }) => {
   return (
     <div className="d-block mx-auto mt-2 container w-50" key={key}>
       <CAudio track={track} playlist={playlist} personal />
@@ -42,10 +41,12 @@ const PlaylistTracksList = ({ playlistTracks }) => {
 
 const SortableList = SortableContainer(PlaylistTracksList);
 
-const TracksInPlaylistSortable = ({ playlistTracks, loadTracksToPlaylist }) => {
-  let idPlaylist = history.location.pathname.substring(
-    history.location.pathname.lastIndexOf("/") + 1
-  );
+const TracksInPlaylistSortable = ({
+  route,
+  playlistTracks,
+  loadTracksToPlaylist,
+}) => {
+  let idPlaylist = route?.params?._id;
   const [newPlaylistTracks, setNewPlaylistTracks] = useState(playlistTracks);
   const onSortEnd = (e) => {
     var newChangedPlaylistTracks = arrayMoveImmutable(
@@ -65,7 +66,7 @@ const TracksInPlaylistSortable = ({ playlistTracks, loadTracksToPlaylist }) => {
   );
 };
 
-const CTracksInPlaylistSortable = connect(null, {
+const CTracksInPlaylistSortable = connect((state) => ({ route: state.route }), {
   loadTracksToPlaylist: actionTracksToPlaylist,
 })(TracksInPlaylistSortable);
 
@@ -86,7 +87,8 @@ const PlaylistTracks = ({ promise, tracks: { _id, url } = {} }) => {
         promiseState={promise}
         children={null}
       />
-      {promise?.playlistTracks?.payload?.tracks ? null : (
+      {promise?.playlistTracks?.status === "RESOLVED" &&
+      promise?.playlistTracks?.payload?.tracks?.length !== 0 ? null : (
         <h2 className="mt-5 text-center">
           {promise?.myUser?.payload?.nick
             ? promise?.myUser?.payload?.nick
@@ -109,10 +111,17 @@ export const MyPlaylistTracks = connect(
 )(({ promise }) => {
   return (
     <div>
-      <h3 className="text-center">
+      <h3 className="text-center mb-3">
         Перетащите аудио файл(-ы) для загрузки в этот плейлист.
       </h3>
-      <CMyDropzone />
+      <CPreloader
+        promiseName={"playlistTracks"}
+        promiseState={promise}
+        children={<CMyDropzone />}
+      />
+      <h6 className="text-center mt-3">
+        Максимальное количество треков в плейлисте - 100 штук.
+      </h6>
       <div className="d-block mx-auto mt-2 container w-50">
         {promise?.playlistTracks?.payload?.tracks &&
         promise?.playlistTracks?.payload?.tracks?.length !== 0 ? (
@@ -149,13 +158,13 @@ const MyPlaylists = ({ promise, onPlaylistCreate }) => {
             onChange={(e) => setPlaylist(e.target.value)}
           />
           <div id="playlistHelp" className="form-text">
-            Название плейлиста может быть от 2 до 10 символов.
+            Название плейлиста может быть от 2 до 15 символов.
           </div>
         </div>
         <button
           type="submit"
           className="btn btn-primary"
-          disabled={playlist.length > 1 && playlist.length < 11 ? false : true}
+          disabled={playlist.length > 1 && playlist.length < 16 ? false : true}
           onClick={() => onPlaylistCreate(playlist)}
         >
           Создать
@@ -169,6 +178,7 @@ const MyPlaylists = ({ promise, onPlaylistCreate }) => {
           ))}
         </ul>
       </div>
+      <div className="container" style={{ height: "300px" }}></div>
     </>
   );
 };

+ 12 - 22
src/components/Tracks.js

@@ -1,12 +1,11 @@
 import { connect } from "react-redux";
-import { CAudio } from "./Audio";
 import { useState, useEffect } from "react";
-import { actionAllTracks, actionFullSkipTracks } from "../actions";
-import { skipValue } from "./../helpers/index";
+import { skipValue } from "../../helpers/index";
+import { CAudio } from "../Audio/Audio";
+import { actionAllTracks, actionFullSkipTracks } from "../../actions/types";
 
 const Tracks = ({
   tracks,
-  skip,
   getAllTracks,
   skipAllTracks,
   search,
@@ -17,7 +16,7 @@ const Tracks = ({
   useEffect(() => {
     if (flag) {
       window.scrollTo(0, 0);
-      getAllTracks(skip);
+      getAllTracks(skipValue);
       setFlag(false);
     }
   }, [flag]);
@@ -43,23 +42,14 @@ const Tracks = ({
 
   return (
     <>
-      {search
-        ? (searchResults || []).map((track, index) => (
-            <CAudio
-              track={track}
-              index={index}
-              playlist={searchResults}
-              key={track._id}
-            />
-          ))
-        : (tracks || []).map((track, index) => (
-            <CAudio
-              track={track}
-              index={index}
-              playlist={tracks}
-              key={track._id}
-            />
-          ))}
+      {(search ? searchResults || [] : tracks || []).map((track, index) => (
+        <CAudio
+          track={track}
+          index={index}
+          playlist={search ? searchResults : tracks}
+          key={track._id}
+        />
+      ))}
     </>
   );
 };

+ 16 - 14
src/components/Profile/MyProfile.js

@@ -1,21 +1,21 @@
-import { Spoiler } from "../Spoiler";
+import { Spoiler } from "../Other/Spoiler";
 import {
   backURL,
   validateEmail,
   validatePassword,
   validateNickname,
 } from "../../helpers/index";
-import {
-  actionSetNickname,
-  actionSetEmail,
-  actionSetNewPassword,
-} from "../../actions/index";
-import { CMyDropzone } from "../Dropzone";
+import { CMyDropzone } from "../Other/Dropzone";
 import { Form, Alert, Row, Col, Button } from "react-bootstrap";
 import { useState } from "react";
 import { connect } from "react-redux";
 import { CUserInfo } from "./UserInfo";
-import { CPreloader } from "./../Preloader";
+import { CPreloader } from "../Other/Preloader";
+import {
+  actionSetNickname,
+  actionSetEmail,
+  actionSetNewPassword,
+} from "./../../actions/types";
 
 const MyProfile = ({
   id,
@@ -57,6 +57,7 @@ const MyProfile = ({
                   method="post"
                   encType="multipart/form-data"
                   id="form"
+                  className="text-center"
                 >
                   <img
                     className="avatarProfile"
@@ -92,7 +93,7 @@ const MyProfile = ({
                     className="m-2"
                     controlId="formHorizontalEmail"
                   >
-                    <Form.Label column sm={2}>
+                    <Form.Label column sm={5}>
                       Ваш никнейм:
                     </Form.Label>
                     <Col sm={10}>
@@ -113,7 +114,7 @@ const MyProfile = ({
                     className="m-2"
                     controlId="formHorizontalEmail"
                   >
-                    <Form.Label column sm={2}>
+                    <Form.Label column sm={5}>
                       Новый никнейм:
                     </Form.Label>
                     <Col sm={10}>
@@ -168,7 +169,7 @@ const MyProfile = ({
                     className="m-2"
                     controlId="formHorizontalEmail"
                   >
-                    <Form.Label column sm={2}>
+                    <Form.Label column sm={5}>
                       Ваша почта:
                     </Form.Label>
                     <Col sm={10}>
@@ -185,7 +186,7 @@ const MyProfile = ({
                     className="m-2"
                     controlId="formHorizontalEmail"
                   >
-                    <Form.Label column sm={2}>
+                    <Form.Label column sm={5}>
                       Новая почта:
                     </Form.Label>
                     <Col sm={10}>
@@ -245,7 +246,7 @@ const MyProfile = ({
                     className="m-2"
                     controlId="formHorizontalPassword"
                   >
-                    <Form.Label column sm={2}>
+                    <Form.Label column sm={5}>
                       Пароль:
                     </Form.Label>
                     <Col sm={10}>
@@ -268,7 +269,7 @@ const MyProfile = ({
                     className="m-2"
                     controlId="formHorizontalPassword"
                   >
-                    <Form.Label column sm={2}>
+                    <Form.Label column sm={5}>
                       Новый пароль:
                     </Form.Label>
                     <Col sm={10}>
@@ -303,6 +304,7 @@ const MyProfile = ({
           />
         </div>
       ) : null}
+      <div className="container" style={{ height: "300px" }}></div>
     </div>
   );
 };

+ 40 - 60
src/components/Profile/UserInfo.js

@@ -13,76 +13,56 @@ const InfoRender = ({ avatar, nick, date }) => {
   ];
   return (
     <>
-    {date ? <div className="text-center">
-    <h1 className="text-center">Информация о профиле пользователя:</h1>
-      <div>
-        <img
-          style={{ width: "6rem" }}
-          src={
-            avatar
-              ? `${backURL}/${avatar}`
-              : "https://i.ibb.co/bBxzmTm/default-avatar.jpg"
-          }
-          className="card-img-top"
-          alt="ProfileImage"
-        />
-      </div>
-      <div>Никнейм: {nick}</div>
-      <div>Дата регистрации: {dmy.join(".")}</div>
-    </div> : <Page404 />}
+      {date ? (
+        <div className="text-center">
+          <h1 className="text-center">Информация о профиле пользователя:</h1>
+          <div>
+            <img
+              style={{ width: "6rem" }}
+              src={
+                avatar
+                  ? `${backURL}/${avatar}`
+                  : "https://i.ibb.co/bBxzmTm/default-avatar.jpg"
+              }
+              className="card-img-top"
+              alt="ProfileImage"
+            />
+          </div>
+          <div>Никнейм: {nick}</div>
+          <div>Дата регистрации: {dmy.join(".")}</div>
+        </div>
+      ) : (
+        <Page404 />
+      )}
     </>
   );
 };
 
 const UserInfo = ({ id, auth, promise }) => {
   return (
-    <>
-      {id === auth?.payload?.sub?.id ? (
-        <InfoRender
-          avatar={promise?.myUser?.payload?.avatar?.url}
-          nick={promise?.myUser?.payload?.nick}
-          date={promise?.myUser?.payload?.createdAt}
-        />
-      ) : (
-        <InfoRender
-          avatar={promise?.anotherUser?.payload?.avatar?.url}
-          nick={
-            promise?.anotherUser?.payload?.nick
-              ? promise?.anotherUser?.payload?.nick
-              : promise?.anotherUser?.payload?.login
-          }
-          date={promise?.anotherUser?.payload?.createdAt}
-        />
-      )}
-    </>
+    <InfoRender
+      avatar={
+        id === auth?.payload?.sub?.id
+          ? promise?.myUser?.payload?.avatar?.url
+          : promise?.anotherUser?.payload?.avatar?.url
+      }
+      nick={
+        id === auth?.payload?.sub?.id
+          ? promise?.myUser?.payload?.nick
+          : promise?.anotherUser?.payload?.nick
+          ? promise?.anotherUser?.payload?.nick
+          : promise?.anotherUser?.payload?.login
+      }
+      date={
+        id === auth?.payload?.sub?.id
+          ? promise?.myUser?.payload?.createdAt
+          : promise?.anotherUser?.payload?.createdAt
+      }
+    />
   );
 };
 
-// {promise?.myTracks?.status === "RESOLVED" ||
-//       promise?.anotherUserTracks?.status === "RESOLVED" ? (
-//         id === auth?.payload?.sub?.id ? (
-//           promise?.myTracks?.payload ? (
-//             <InfoRender
-//               avatar={promise?.myUser?.payload?.avatar?.url}
-//               nick={promise?.myUser?.payload?.nick}
-//               tracksCount={promise?.myTracks?.payload?.length}
-//             />
-//           ) : null
-//         ) : promise?.anotherUserTracks?.payload ? (
-//           <InfoRender
-//             avatar={promise?.anotherUser?.payload?.avatar?.url}
-//             nick={promise?.anotherUser?.payload?.nick}
-//             tracksCount={promise?.anotherUserTracks?.payload?.length}
-//           />
-//         ) : null
-//       ) : (
-//         <Loader />
-//       )}
-
 export const CUserInfo = connect(
   (state) => ({ auth: state.auth, promise: state.promise }),
   null
 )(UserInfo);
-
-// findMyTracks: actionFindMyTracks,
-// findAnotherUserTracks: actionFindAnotherUserTracks,

+ 56 - 28
src/helpers/index.js

@@ -1,4 +1,4 @@
-import { useState } from "react";
+import { states } from "../App";
 
 export const skipValue = 30;
 
@@ -50,32 +50,60 @@ export function validateNickname(nick) {
 
 export const delay = (ms) => new Promise((ok) => setTimeout(() => ok(ms), ms));
 
-export const useLocalStoredState = (defaultState, localStorageName) => {
-  let payload;
-  try {
-    payload = JSON.parse(localStorage[localStorageName]);
-  } catch {
-    payload = defaultState;
-  }
-  const [state, setState] = useState(payload);
-  return [
-    state,
-    (newState) => {
-      setState(newState);
-      localStorage.setItem(localStorageName, newState);
-    },
-  ];
+export const queries = {
+  "/profile/:_id": (match) => ({
+    name: `${
+      match.params._id === states?.auth?.payload?.sub?.id
+        ? "myUser"
+        : "anotherUser"
+    }`,
+    query: `query findUser($q:String){
+      UserFindOne(query:$q){
+        _id login nick createdAt avatar {
+          _id url
+        }
+      }
+    }
+`,
+    variables: { q: JSON.stringify([{ ___owner: match.params._id }]) },
+  }),
+  "/myplaylist/:_id": (match) => ({
+    name: "playlistTracks",
+    query: `query playlistTracks($q:String) {
+      PlaylistFindOne(query:$q) {
+      _id name tracks {_id url originalFileName} owner {_id login}
+      }
+     }`,
+    variables: { q: JSON.stringify([{ _id: match.params._id }]) },
+  }),
 };
 
-export const useProxyState = (defaultState) => {
-  const [state, setState] = useState(defaultState);
-  return new Proxy(state, {
-    get(obj, key) {
-      return obj[key];
-    },
-    set(obj, key, value) {
-      setState({ ...obj, [key]: value });
-      return true;
-    },
-  });
-};
+// export const useLocalStoredState = (defaultState, localStorageName) => {
+//   let payload;
+//   try {
+//     payload = JSON.parse(localStorage[localStorageName]);
+//   } catch {
+//     payload = defaultState;
+//   }
+//   const [state, setState] = useState(payload);
+//   return [
+//     state,
+//     (newState) => {
+//       setState(newState);
+//       localStorage.setItem(localStorageName, newState);
+//     },
+//   ];
+// };
+
+// export const useProxyState = (defaultState) => {
+//   const [state, setState] = useState(defaultState);
+//   return new Proxy(state, {
+//     get(obj, key) {
+//       return obj[key];
+//     },
+//     set(obj, key, value) {
+//       setState({ ...obj, [key]: value });
+//       return true;
+//     },
+//   });
+// };

BIN
src/images/gifka.gif


+ 1 - 1
src/pages/Library.js

@@ -1,5 +1,5 @@
 import { connect } from "react-redux";
-import { CMyPlaylists } from "../components/Playlist";
+import { CMyPlaylists } from "../components/Playlist/Playlist";
 
 const Library = ({ promise }) => {
   return (

+ 1 - 1
src/pages/Login.js

@@ -1,8 +1,8 @@
 import { useState } from "react";
-import { actionFullLogin } from "./../actions/index";
 import { Form, Row, Col, Button, Alert } from "react-bootstrap";
 import { connect } from "react-redux";
 import { Link } from "react-router-dom";
+import { actionFullLogin } from "../actions/types";
 
 const LoginForm = ({ promise, onLogin }) => {
   const [login, setLogin] = useState("");

+ 4 - 11
src/pages/Profile.js

@@ -1,19 +1,12 @@
 import { connect } from "react-redux";
-import { history } from "./../App";
 import { CMyProfile } from "./../components/Profile/MyProfile";
-import { actionAboutMe, actionAboutAnotherUser } from "./../actions/index";
 
-const Profile = ({ promise, auth, aboutMe, aboutAnotherUser }) => {
-  let currentUserId = history.location.pathname.substring(
-    history.location.pathname.lastIndexOf("/") + 1
-  );
+const Profile = ({ route }) => {
+  let currentUserId = route?.params?._id;
   return <CMyProfile id={currentUserId} />;
 };
 
 export const CProfile = connect(
-  (state) => ({ promise: state.promise, auth: state.auth }),
-  {
-    aboutMe: actionAboutMe,
-    aboutAnotherUser: actionAboutAnotherUser,
-  }
+  (state) => ({ route: state.route }),
+  null
 )(Profile);

+ 1 - 1
src/pages/Register.js

@@ -1,9 +1,9 @@
 import { useState } from "react";
-import { actionFullRegister } from "./../actions/index";
 import { Form, Row, Col, Button, Alert } from "react-bootstrap";
 import { connect } from "react-redux";
 import { Link } from "react-router-dom";
 import { validateEmail, validatePassword } from "./../helpers/index";
+import { actionFullRegister } from "./../actions/types";
 
 const RegisterForm = ({ promise, auth, onRegister }) => {
   const [login, setLogin] = useState("");

+ 24 - 13
src/pages/Search.js

@@ -1,9 +1,9 @@
 import { connect } from "react-redux";
-import { CTracks } from "../components/Tracks";
-import { PlayerHeader } from "./../components/PlayerHeader";
+import { CTracks } from "../components/Playlist/SearchTracks";
+import { PlayerHeader } from "../components/Playlist/PlayerHeader";
 import { useState } from "react";
-import { actionSearch, actionSetSearch } from "./../actions/index";
-import { CPreloader } from "./../components/Preloader";
+import { CPreloader } from "../components/Other/Preloader";
+import { actionSearch, actionSetSearch } from "../actions/types";
 
 const SearchField = connect(null, {
   onChangeSearch: actionSearch,
@@ -11,23 +11,34 @@ const SearchField = connect(null, {
 })(({ onChangeSearch, setSearch }) => {
   const [text, setText] = useState("");
   return (
-    <div className="input-group rounded">
+    <div className="input-group mb-3">
       <input
-        type="search"
-        className="form-control rounded"
+        type="text"
+        className="form-control"
         placeholder="Поиск по всей музыке"
         aria-label="Поиск"
-        aria-describedby="search-addon"
-        value={text}
+        aria-describedby="basic-addon2"
+        onFocus={() => {
+          setSearch(true);
+        }}
         onChange={(e) => {
           setText(e.target.value);
           onChangeSearch(text);
         }}
-        onFocus={() => {
-          setSearch(true);
-        }}
-        onBlur={() => setSearch(false)}
+        value={text}
       />
+      <div className="input-group-append">
+        <button
+          className="btn btn-dark"
+          type="button"
+          onClick={() => {
+            setSearch(false);
+            setText("");
+          }}
+        >
+          Отменить
+        </button>
+      </div>
     </div>
   );
 });

+ 1 - 4
src/reducers/index.js

@@ -33,6 +33,7 @@ export function authReducer(state, { type, token }) {
   }
   if (type === "AUTH_LOGOUT") {
     localStorage.removeItem("authToken");
+    window.location.reload();
     return {};
   }
   return state;
@@ -167,7 +168,3 @@ export function scrollTracksReducer(
   }
   return state;
 }
-
-// loadedTracks:state?.loadedTracks
-//         ? [...state.loadedTracks, ...newTracks]
-//         : [...newTracks],