瀏覽代碼

first steps

mfdok43 2 年之前
父節點
當前提交
038ae106a7
共有 19 個文件被更改,包括 3591 次插入65 次删除
  1. 5 0
      .idea/.gitignore
  2. 6 0
      .idea/misc.xml
  3. 8 0
      .idea/modules.xml
  4. 12 0
      .idea/music-player.iml
  5. 6 0
      .idea/vcs.xml
  6. 2996 44
      package-lock.json
  7. 7 1
      package.json
  8. 16 16
      src/App.js
  9. 34 4
      src/App.scss
  10. 18 0
      src/actions/index.js
  11. 144 0
      src/pages/aside.js
  12. 4 0
      src/pages/content.js
  13. 7 0
      src/pages/footer.js
  14. 85 0
      src/pages/header.js
  15. 9 0
      src/pages/index.js
  16. 19 0
      src/pages/main.js
  17. 3 0
      src/pages/pageMain.js
  18. 58 0
      src/pages/userTracks.js
  19. 154 0
      src/reducers/index.js

+ 5 - 0
.idea/.gitignore

@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/

+ 6 - 0
.idea/misc.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JavaScriptSettings">
+    <option name="languageLevel" value="JSX" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/music-player.iml" filepath="$PROJECT_DIR$/.idea/music-player.iml" />
+    </modules>
+  </component>
+</project>

+ 12 - 0
.idea/music-player.iml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/.tmp" />
+      <excludeFolder url="file://$MODULE_DIR$/temp" />
+      <excludeFolder url="file://$MODULE_DIR$/tmp" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

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


+ 7 - 1
package.json

@@ -6,9 +6,14 @@
     "@testing-library/jest-dom": "^5.16.1",
     "@testing-library/react": "^12.1.2",
     "@testing-library/user-event": "^13.5.0",
+    "node-sass": "^7.0.1",
     "react": "^17.0.2",
     "react-dom": "^17.0.2",
+    "react-redux": "^7.2.6",
+    "react-router-dom": "^5.3.0",
     "react-scripts": "5.0.0",
+    "redux": "^4.1.2",
+    "redux-thunk": "^2.4.1",
     "web-vitals": "^2.1.2"
   },
   "scripts": {
@@ -34,5 +39,6 @@
       "last 1 firefox version",
       "last 1 safari version"
     ]
-  }
+  },
+  "proxy": "http://сервер.a-level.com.ua/"
 }

+ 16 - 16
src/App.js

@@ -1,25 +1,25 @@
-import logo from './logo.svg';
-import './App.css';
+import './App.scss';
+import {Provider}   from 'react-redux';
+import store from "./reducers";
+import {Footer, Header, Main} from "./pages";
+import {Router} from 'react-router-dom';
+import {history} from "./reducers";
+
 
 function App() {
   return (
+      <Router history={history}>
+          <Provider store={store}>
     <div className="App">
-      <header className="App-header">
-        <img src={logo} className="App-logo" alt="logo" />
-        <p>
-          Edit <code>src/App.js</code> and save to reload.
-        </p>
-        <a
-          className="App-link"
-          href="https://reactjs.org"
-          target="_blank"
-          rel="noopener noreferrer"
-        >
-          Learn React
-        </a>
-      </header>
+     <Header />
+     <Main />
+     <Footer />
     </div>
+    </Provider>
+      </Router>
   );
 }
 
+
 export default App;
+

+ 34 - 4
src/App.scss

@@ -1,5 +1,5 @@
 .App {
-
+  height: auto;
   text-align: center;
 }
 
@@ -7,18 +7,48 @@
   height: 100px;
 }
 
-
-
 .Header {
   display: flex;
   justify-content: space-between;
   background: #fc0874;
-height: 140px;
+height: 120px;
+}
+
+main {
+  width: 99%;
+  display: flex;
+  flex-direction: row;
+  aside {
+    width: 20%;
+    background-color: cyan;
+  }
+
+}
+
+
+footer {
+  height: 200px;
+  background-color: #303030;
 }
 
 
 .LoginButtons {
+  display: flex;
+  flex-direction: row;
      margin-top: 40px;
   margin-right: 20px;
 }
 
+.RegColumn {
+  display: flex;
+  flex-direction: column;
+}
+
+
+.LoginColumn {
+  display: flex;
+  flex-direction: column;
+  margin-top: 30px;
+  margin-right: 20px;
+}
+

+ 18 - 0
src/actions/index.js

@@ -0,0 +1,18 @@
+import store, {actionPromise, gql} from "../reducers";
+
+export const actionPlaylistFind = () => actionPromise('playlistFind', gql(`query {
+            PlaylistFind(query: "[{\\"parent\\":null}]"){
+                _id owner {login} tracks {url originalFileName}
+            }
+        }`))
+store.dispatch(actionPlaylistFind())
+
+export const actionPlaylistById = (_id) => actionPromise('playlistById1', gql(`query playlistById($q: String){
+            PlaylistFindOne(query: $q){
+                 _id owner {login} tracks {_id url originalFileName}
+            }
+        }`, { q: JSON.stringify([{ _id }]) }))
+
+store.dispatch(actionPlaylistById("5ff7ff36e926687ee86b0b1d"))
+
+export const actionPlayTrack = () => console.log('kak dela kak dela');

+ 144 - 0
src/pages/aside.js

@@ -0,0 +1,144 @@
+import {connect} from "react-redux";
+import {Link} from "react-router-dom";
+import {actionPlaylistFind} from "../actions";
+
+const defaultPlaylists = [
+    {
+        "_id": "5fe25a677aa9837fd3e741d9",
+        "owner": {
+            "login": "123"
+        },
+        "tracks": null
+    },
+    {
+        "_id": "5ff7fddee926687ee86b0b17",
+        "owner": {
+            "login": "uu"
+        },
+        "tracks": [
+            {
+                "url": "track/061766012c089e677a18bd7d7fcc132a",
+                "originalFileName": "01 - Bored.mp3"
+            },
+            {
+                "url": "track/5fe385a7af766b71f1bcaefe723dfb77",
+                "originalFileName": "03 - One Weak.mp3"
+            }
+        ]
+    },
+
+
+    {
+        "_id": "5ff7ff36e926687ee86b0b1d",
+        "owner": {
+            "login": "uu"
+        },
+        "tracks": [
+            {
+                "url": "track/1717d07cd1a5876b51c8e3d00c1fa123",
+                "originalFileName": "02 - Minus Blindfold.mp3"
+            },
+            {
+                "url": "track/011dc1fafb88494e2b2fcdd5408c9fc8",
+                "originalFileName": "09 - Engine No.9.mp3"
+            },
+            {
+                "url": "track/f926ed49d35036d952fb34426dbc7d0e",
+                "originalFileName": "10 - Fireal.mp3"
+            }
+        ]
+    },
+
+
+
+    {
+        "_id": "5fe0f8097aa9837fd3e74192",
+        "owner": {
+            "login": "Windforce"
+        },
+        "tracks": [
+            {
+                "url": "track/a37387adcf96795d5a49d4d7e09c0472",
+                "originalFileName": "01-Cluster One.mp3"
+            },
+            {
+                "url": "track/3164a657cf44dd734344ed3ebcde625f",
+                "originalFileName": "Роман, заставь его надеть пиджак!.mp3"
+            },
+            {
+                "url": "track/4685efea193d2d48d5ac56fc2ab767d8",
+                "originalFileName": "07-Take It Back.mp3"
+            },
+            {
+                "url": "track/59e061cc86750556dfae0f83708da571",
+                "originalFileName": "10-Lost For Words.mp3"
+            }
+        ]
+    },
+    {
+        "_id": "5fe35ffbe926687ee86b0a5c",
+        "owner": {
+            "login": "newUser"
+        },
+        "tracks": [
+            {
+                "url": "track/9251ba5dd154807fb10fe0d9dfe4c804",
+                "originalFileName": "01 - God Loves Only You.mp3"
+            },
+            {
+                "url": "track/a9c945eebf4cd5e829c470f8f0672ddf",
+                "originalFileName": "03 - Over The Love.mp3"
+            },
+            {
+                "url": "track/57cd3b9b7cb7d6f216c07fb46374fce5",
+                "originalFileName": "04 - Talk Too Much.mp3"
+            },
+            {
+                "url": "track/b1b2c99969d210f3d70f5f1fad651732",
+                "originalFileName": "07 - You'Re Too Expensive.mp3"
+            },
+            {
+                "url": "track/a8d8330e2c4ddfa00db8f285d6f7ec0b",
+                "originalFileName": "08 - My Love Will Fall.mp3"
+            },
+            {
+                "url": "track/9a6f3d3c2039a5f3e15a367f5f2a3675",
+                "originalFileName": "10 - Feeling The Itch.mp3"
+            },
+            {
+                "url": "track/2fc841aff4003b3dd4d0c814f1f9651d",
+                "originalFileName": "11 - You Can't Always Do What You Like.mp3"
+            }
+        ]
+    },]
+
+
+
+const Playlist = ({playlist:{_id, owner:{login},tracks}={}}) =>
+    <div>
+        {console.log(tracks)}
+        {tracks !== null ?
+            tracks.length > 0 ? (<li>
+            <Link to={`/playlist/${_id}`}>{login}</Link>
+        </li>) : <></> : <></>}
+    </div>
+
+
+
+const Playlists = ({playlists=defaultPlaylists}) =>{
+        return (
+            <ul className='Users'>
+                    {playlists.map(playlist =>  <Playlist playlist={playlist}/> )}
+            </ul>
+        )
+
+}
+
+const CPlaylists = connect(state => ({playlists: state.promise.playlistFind?.payload || []}))
+(Playlists)
+
+export const Aside = () =>
+    <aside>
+            <CPlaylists />
+    </aside>
+

+ 4 - 0
src/pages/content.js

@@ -0,0 +1,4 @@
+export const Content = ({children}) =>
+    <div>
+        {children}
+    </div>

+ 7 - 0
src/pages/footer.js

@@ -0,0 +1,7 @@
+import {Logo} from "./header";
+
+export const Footer = () =>
+    <footer>
+        <Logo />
+        <h2>Тут будет всякое</h2>
+    </footer>

+ 85 - 0
src/pages/header.js

@@ -0,0 +1,85 @@
+import logo from "../logo.svg";
+import {useState} from 'react'
+import {connect} from 'react-redux';
+import {actionAuthLogout, actionFullReg, actionFullLogin} from "../reducers"
+import {history} from "../reducers";
+import {Route, Link, Switch} from 'react-router-dom';
+
+export const Logo = () =>
+<Link to='/'><img src={logo} className="App-logo" alt="logo" /></Link>
+
+export const Header = () =>
+    <header className="Header">
+        <Logo />
+        <Switch>
+            <Route path="/login" component={CLoginForm}/>
+            <Route path="/registration" component={CRegForm}/>
+            <Route path='*' component={CLoginButtons} />
+        </Switch>
+    </header>
+
+const LoginButtons = ({onLogout, history, token}) => {
+
+    return (
+        <>
+            {!token ?
+                ( <div className='LoginButtons'>
+                    <Link to='/login'><button onClick={() => history.push('/')}>Вход</button></Link>
+                    <Link to='/registration'><button>Регистрация</button></Link></div>) :
+                (<div className='LoginColumn'><strong>{JSON.parse(atob(token.split(".")[1])).sub.login}
+                </strong>
+                    <button onClick={() => {onLogout(); history.push('/')}}>Выйти</button></div>)
+            }
+        </>
+    )
+}
+const CLoginButtons = connect(state => ({token: state.auth.token}), {onLogout: actionAuthLogout})(LoginButtons)
+
+const LoginForm = ({onLogin, history}) => {
+    const [l, setL] = useState ('')
+    const [p, setP] = useState ('')
+
+
+
+    return (
+        <div className='LoginColumn'>
+            <input placeholder='Введите имя' style={{backgroundColor:"skyblue"}} onChange={e => setL(e.target.value)}></input>
+            <input type='password' placeholder='Введите пароль' style={{backgroundColor:"skyblue"}} onChange={e => setP(e.target.value)}></input>
+            <button onClick={() => {onLogin(l,p); history.push('/')}}>Войти</button>
+        </div>
+    )
+}
+
+const CLoginForm = connect (null,{onLogin: actionFullLogin})(LoginForm)
+
+const RegForm = ({ onReg }) => {
+    const [l, setL] = useState("");
+    const [p, setP] = useState("");
+    const [p2, setP2] = useState('')
+
+    return (
+        <div className='LoginButtons'>
+
+            <div className='RegColumn'>
+                         <input placeholder='Введите имя'
+                                style={{ backgroundColor: "skyblue" }}
+                                onChange={(e) => setL(e.target.value)}></input>
+
+                         <input placeholder='Введите пароль'
+                                style={{ backgroundColor: "skyblue" }}
+                                onChange={(e) => setP(e.target.value)}></input>
+            <div>{p.length < 6 ? 'Короткий пароль' : 'Хороший пароль'}</div>
+            </div>
+            <div className='RegColumn'>
+            <button disabled={p.length >= 6 && p === p2 && l !== "" ? false : true} onClick={() => {onReg(l, p); history.push('/')}}>Регистрация</button>
+                         <input placeholder='Подтвердите пароль'
+                                style={{ backgroundColor: "skyblue" }}
+                                onChange={(e) => setP2(e.target.value)}></input>
+            <div>{p === p2 ? 'Пароли совпадают' : 'Пароли не совпадают'}</div>
+            </div>
+        </div>
+    );
+};
+
+const CRegForm = connect(null,{onReg: actionFullReg}) (RegForm)
+

+ 9 - 0
src/pages/index.js

@@ -0,0 +1,9 @@
+import {Header} from "./header";
+import {Main} from "./main";
+import {Footer} from "./footer";
+import {PageMain} from "./pageMain";
+import {UserTracks} from "./userTracks";
+import {Aside} from "./aside";
+import {Content} from "./content";
+
+export {Header, Main, Footer, PageMain, UserTracks, Aside, Content}

+ 19 - 0
src/pages/main.js

@@ -0,0 +1,19 @@
+import {Route, Redirect, Switch} from 'react-router-dom';
+import {Content} from "./content";
+import {PageMain} from "./pageMain";
+import {CUserTracks} from "./userTracks";
+import {Aside} from "./aside";
+
+
+
+export const Main = () =>
+    <main>
+        <Aside />
+        <Content>
+            <Redirect from='/main' to='/'/>
+            <Switch>
+                <Route path='/' component={PageMain} exact/>
+                <Route path="/playlist/:_id" component={CUserTracks}/>
+            </Switch>
+        </Content>
+   </main>

+ 3 - 0
src/pages/pageMain.js

@@ -0,0 +1,3 @@
+export const PageMain = () =>
+    <h1>Главная страница</h1>
+

+ 58 - 0
src/pages/userTracks.js

@@ -0,0 +1,58 @@
+import {connect} from "react-redux";
+import {actionPlaylistById, actionPlayTrack} from "../actions";
+import {backURL} from "../reducers";
+import {useEffect} from 'react';
+
+import {history} from "../reducers";
+
+const defaultTracks = [
+    {
+        "_id": "5fe35e5be926687ee86b0a49",
+        "url": "track/7352b53f3af39db41ffb152acadd11b2",
+        "originalFileName": "Фруктовый Кефир - Тому, кто не дружит со своей головой (zoop.su).mp3"
+    },
+    {
+        "_id": "5fe35e5be926687ee86b0a4a",
+        "url": "track/254f8d37a4c62ccef0bf1834a43eac54",
+        "originalFileName": "Фруктовый Кефир - Убиваю время (zoop.su).mp3"
+    },
+    {
+        "_id": "5fe35e5be926687ee86b0a4b",
+        "url": "track/710d8b4ee31df560f7d46dd745cce690",
+        "originalFileName": "Фруктовый Кефир - Туча (zoop.su).mp3"
+    },
+    {
+        "_id": "5fe35e5ce926687ee86b0a4c",
+        "url": "track/76c3e402d9ed7b3e54640462af7e68bf",
+        "originalFileName": "Фруктовый Кефир - Капюшон. Парашют (zoop.su).mp3"
+    },
+    {
+        "_id": "5fe35e5ce926687ee86b0a4d",
+        "url": "track/6209eae2563b6a3d0471663fcaf0aede",
+        "originalFileName": "Фруктовый Кефир - На своей Волне (zoop.su).mp3"
+    },]
+
+const Track = ({track:{_id,url,originalFileName}=defaultTracks, onPlayTrack}) =>
+    <div>
+     <audio controls src={backURL+'/'+url}></audio>
+    </div>
+
+const CTrack = connect(null, {onPlayTrack: actionPlayTrack})(Track)
+
+const Tracks = ({playlist:{tracks}={}}) =>
+    <div>
+        {(tracks || []).map(track => <CTrack track={track}/>)}
+    </div>
+
+const CTracks = connect(state => ({playlist: state.promise.playlistById1?.payload}))(Tracks)
+
+
+export const UserTracks = ({match:{params:{_id}}, getData}, history) => {
+    useEffect(() => {
+        getData(_id)
+    }, [_id])
+    return (
+        <CTracks/>
+    )}
+
+export const CUserTracks = connect(null, {getData: actionPlaylistById})(UserTracks)

+ 154 - 0
src/reducers/index.js

@@ -0,0 +1,154 @@
+import {createStore, combineReducers, applyMiddleware} from 'redux';
+import thunk from 'redux-thunk';
+import createHistory from "history/createBrowserHistory";
+
+export const history = createHistory()
+
+
+const getGQL = url =>
+    async (query, variables = {}) => {
+        let obj = await fetch(url, {
+            method: 'POST',
+            headers: {
+                "Content-Type": "application/json",
+                Authorization: localStorage.authToken ? 'Bearer ' + localStorage.authToken : {},
+            },
+            body: JSON.stringify({ query, variables })
+        })
+        let a = await obj.json()
+        if (!a.data && a.errors)
+            throw new Error(JSON.stringify(a.errors))
+        return a.data[Object.keys(a.data)[0]]
+    }
+
+export const backURL = 'http://player.asmer.fs.a-level.com.ua'
+
+export const gql = getGQL(backURL + '/graphql');
+
+
+const jwtDecode = token => {
+    try {
+        let arrToken = token.split('.')
+        let base64Token = atob(arrToken[1])
+        return JSON.parse(base64Token)
+    }
+    catch (e) {
+        console.log('Лажа, Бро ' + e);
+    }
+}
+
+function authReducer(state, { type, token }) {
+    if (!state) {
+        if (localStorage.authToken) {
+            type = 'AUTH_LOGIN'
+            token = localStorage.authToken
+        } else state = {}
+    }
+    if (type === 'AUTH_LOGIN') {
+        localStorage.setItem('authToken', token)
+        let payload = jwtDecode(token)
+        if (typeof payload === 'object') {
+            return {
+                ...state,
+                token,
+                payload
+            }
+        } else return state
+    }
+    if (type === 'AUTH_LOGOUT') {
+        localStorage.removeItem('authToken')
+        return {}
+    }
+    return state
+}
+
+const actionLogin = (login, password) => {
+    return actionPromise(
+        "login",
+        gql(
+            `query log($login:String!, $password:String!) {
+    login(login:$login, password:$password)
+    }`,
+            {login, password }
+        )
+    );
+};
+
+function promiseReducer(state = {}, { type, status, payload, error, name }) {
+    if (type === 'PROMISE') {
+        return {
+            ...state,
+            [name]: { status, payload, error }
+        }
+    }
+    return state;
+}
+
+
+const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token })
+export const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })
+export const actionFullLogin = (login, password) =>
+    async function i(dispatch) {
+        let token = await dispatch(actionLogin(login, password));
+        if (token) {
+            dispatch(actionAuthLogin(token));
+        }
+    }
+
+
+
+const actionPending = name => ({ type: 'PROMISE', status: 'PENDING', name })
+const actionResolved = (name, payload) => ({ type: 'PROMISE', status: 'RESOLVED', name, payload })
+const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error })
+export const actionPromise = (name, promise) =>
+    async dispatch => {
+        dispatch(actionPending(name))
+        try {
+            let data = await promise
+            dispatch(actionResolved(name, data))
+            return data
+        }
+        catch (error) {
+            dispatch(actionRejected(name, error))
+        }
+    }
+
+
+
+export const actionFullReg = (login, password) =>
+    async function a(dispatch) {
+        try {
+            await dispatch(actionReg(login, password));
+        } catch (e) {
+            return 0;
+        }
+        await dispatch(actionFullLogin(login, password));
+    }
+
+const actionReg = (login, password) => {
+    return actionPromise(
+        "reg",
+        gql(
+            `mutation reg($l: String!, $p: String!){
+        createUser(login: $l, password:$p){
+          _id login
+        }
+      }`,
+            { l: login, p: password }
+        )
+    );
+};
+
+
+const store = createStore(combineReducers({promise: promiseReducer,
+        auth: authReducer,}),
+        applyMiddleware(thunk))
+
+store.subscribe(() => console.log(store.getState()))
+
+export default store
+
+
+
+
+