박진수

Merge branch 'react' into 'master'

React

Add some files and dir for react-app

See merge request !1
1 -.idea 1 +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 -node_modules 2 +
3 +# dependencies
4 +/frontend/node_modules
5 +/.pnp
6 +.pnp.js
7 +
8 +# testing
9 +/coverage
10 +
11 +# production
12 +/build
13 +
14 +# misc
15 +.DS_Store
16 +.env.local
17 +.env.development.local
18 +.env.test.local
19 +.env.production.local
20 +
21 +npm-debug.log*
22 +yarn-debug.log*
23 +yarn-error.log*
......
This diff could not be displayed because it is too large.
1 +{
2 + "name": "khuwitch",
3 + "version": "0.1.0",
4 + "private": true,
5 + "dependencies": {
6 + "@material-ui/core": "^4.11.1",
7 + "@material-ui/icons": "^4.9.1",
8 + "@testing-library/jest-dom": "^5.11.4",
9 + "@testing-library/react": "^11.1.0",
10 + "@testing-library/user-event": "^12.1.10",
11 + "react": "^17.0.1",
12 + "react-dom": "^17.0.1",
13 + "react-scripts": "4.0.1",
14 + "web-vitals": "^0.2.4"
15 + },
16 + "scripts": {
17 + "start": "react-scripts start",
18 + "build": "react-scripts build",
19 + "test": "react-scripts test",
20 + "eject": "react-scripts eject"
21 + },
22 + "eslintConfig": {
23 + "extends": [
24 + "react-app",
25 + "react-app/jest"
26 + ]
27 + },
28 + "browserslist": {
29 + "production": [
30 + ">0.2%",
31 + "not dead",
32 + "not op_mini all"
33 + ],
34 + "development": [
35 + "last 1 chrome version",
36 + "last 1 firefox version",
37 + "last 1 safari version"
38 + ]
39 + }
40 +}
No preview for this file type
1 +<!DOCTYPE html>
2 +<html lang="en">
3 + <head>
4 + <meta charset="utf-8" />
5 + <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
6 + <meta name="viewport" content="width=device-width, initial-scale=1" />
7 + <meta name="theme-color" content="#000000" />
8 + <meta
9 + name="description"
10 + content="Web site created using create-react-app"
11 + />
12 + <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
13 + <!--
14 + manifest.json provides metadata used when your web app is installed on a
15 + user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
16 + -->
17 + <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
18 + <!--
19 + Notice the use of %PUBLIC_URL% in the tags above.
20 + It will be replaced with the URL of the `public` folder during the build.
21 + Only files inside the `public` folder can be referenced from the HTML.
22 +
23 + Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
24 + work correctly both with client-side routing and a non-root public URL.
25 + Learn how to configure a non-root public URL by running `npm run build`.
26 + -->
27 + <title>Khuwitch</title>
28 + </head>
29 + <body>
30 + <noscript>You need to enable JavaScript to run this app.</noscript>
31 + <div id="root"></div>
32 + <!--
33 + This HTML file is a template.
34 + If you open it directly in the browser, you will see an empty page.
35 +
36 + You can add webfonts, meta tags, or analytics to this file.
37 + The build step will place the bundled scripts into the <body> tag.
38 +
39 + To begin the development, run `npm start` or `yarn start`.
40 + To create a production bundle, use `npm run build` or `yarn build`.
41 + -->
42 + </body>
43 +</html>
1 +{
2 + "short_name": "React App",
3 + "name": "Create React App Sample",
4 + "icons": [
5 + {
6 + "src": "favicon.ico",
7 + "sizes": "64x64 32x32 24x24 16x16",
8 + "type": "image/x-icon"
9 + },
10 + {
11 + "src": "logo192.png",
12 + "type": "image/png",
13 + "sizes": "192x192"
14 + },
15 + {
16 + "src": "logo512.png",
17 + "type": "image/png",
18 + "sizes": "512x512"
19 + }
20 + ],
21 + "start_url": ".",
22 + "display": "standalone",
23 + "theme_color": "#000000",
24 + "background_color": "#ffffff"
25 +}
1 +# https://www.robotstxt.org/robotstxt.html
2 +User-agent: *
3 +Disallow:
1 +.App {
2 + text-align: center;
3 +}
4 +
5 +.App-logo {
6 + height: 40vmin;
7 + pointer-events: none;
8 +}
9 +
10 +@media (prefers-reduced-motion: no-preference) {
11 + .App-logo {
12 + animation: App-logo-spin infinite 20s linear;
13 + }
14 +}
15 +
16 +.App-header {
17 + background-color: #282c34;
18 + min-height: 100vh;
19 + display: flex;
20 + flex-direction: column;
21 + align-items: center;
22 + justify-content: center;
23 + font-size: calc(10px + 2vmin);
24 + color: white;
25 +}
26 +
27 +.App-link {
28 + color: #61dafb;
29 +}
30 +
31 +@keyframes App-logo-spin {
32 + from {
33 + transform: rotate(0deg);
34 + }
35 + to {
36 + transform: rotate(360deg);
37 + }
38 +}
1 +import './App.css';
2 +import Login from "./Login";
3 +import Body from "./Body";
4 +
5 +function App() {
6 + let accessToken = null;
7 +
8 + return (
9 + <div className="app">
10 + {!accessToken && <Login />}
11 + {accessToken && <Body />}
12 + </div>
13 + );
14 +}
15 +
16 +export default App;
1 +import { render, screen } from '@testing-library/react';
2 +import App from './App';
3 +
4 +test('renders learn react link', () => {
5 + render(<App />);
6 + const linkElement = screen.getByText(/learn react/i);
7 + expect(linkElement).toBeInTheDocument();
8 +});
1 +.body {
2 + display: flex;
3 + justify-content: center;
4 + background-color: #14141e;
5 +}
...\ No newline at end of file ...\ No newline at end of file
1 +import React from "react";
2 +import "./Body.css";
3 +import {ChannelList} from "./Channel.js"
4 +
5 +function Body(){
6 + return (
7 + <div className="body">
8 + <ChannelList />
9 + </div>
10 + );
11 +}
12 +
13 +export default Body;
...\ No newline at end of file ...\ No newline at end of file
1 +.channel__list {
2 + display: flex;
3 + flex-grow: 1;
4 + flex-direction: column;
5 + flex: 0.3;
6 + min-width: 500px;
7 + height: 100vh;
8 + color: white;
9 + background-color: #0e0e0e;
10 + box-sizing: border-box;
11 +}
12 +
13 +.channel__list__title {
14 + display: flex;
15 + padding: 25px;
16 + width: 30vw;
17 + min-width: 500px;
18 + background-color: #0e0e0e;
19 + font-size: 20px;
20 + font-weight: bold;
21 + position: fixed;
22 + box-sizing: border-box;
23 +}
24 +
25 +.channel__list > ul {
26 + list-style: none;
27 + overflow: auto;
28 + padding-top: 50px;
29 +}
30 +
31 +.channel__list > ul > li > hr {
32 + border: 2px solid #282828;
33 +}
34 +
35 +.channel {
36 + display: flex;
37 + align-items: center;
38 + flex-direction: row;
39 + height: 100px;
40 + background-color: #0e0e0e;
41 +}
42 +
43 +.channel__url {
44 + display: flex;
45 + align-items: center;
46 + flex-grow: 1;
47 + text-decoration: none;
48 + font-size: 14px;
49 + color:white;
50 + box-sizing: border-box;
51 + padding-top: 20px;
52 + padding-bottom: 20px;
53 +}
54 +
55 +.channel__box {
56 + display: flex;
57 + flex-basis: 80%;
58 + flex-direction: row;
59 + justify-content: space-between;
60 +}
61 +
62 +.channel__thumbnail {
63 + height: 25px;
64 + width: 25px;
65 + border-radius: 70%;
66 + overflow: hidden;
67 + margin-right: 30px;
68 + background-color: white;
69 +}
70 +
71 +.channel__icon {
72 + height: 25px;
73 + width: 25px;
74 + border-radius: 70%;
75 + overflow: hidden;
76 + margin-right: 30px;
77 +}
78 +
79 +.channel__info {
80 + flex-basis: 70%;
81 + margin-right: 20px;
82 +}
83 +
84 +.channel__name {
85 + align-content: center;
86 + font-size: 16px;
87 +}
88 +
89 +.channel__game {
90 + font-size: 12px;
91 + color: gray;
92 +}
93 +
94 +.channel__view {
95 + text-align: right;
96 +}
97 +
98 +.play__icon {
99 + margin-left: 10px;
100 + margin-right: 10px;
101 +}
102 +
103 +.pause__icon {
104 + margin-left: 10px;
105 + margin-right: 10px;
106 +}
107 +
108 +
109 +
110 +
1 +import React from "react";
2 +import "./Channel.css";
3 +import "@material-ui/icons"
4 +import PersonIcon from "@material-ui/icons/Person";
5 +import PlayArrowIcon from '@material-ui/icons/PlayArrow';
6 +import PauseIcon from '@material-ui/icons/Pause';
7 +import { WatchOutlined } from "@material-ui/icons";
8 +
9 +export function Channel(props) {
10 + const channel = props.channel;
11 +
12 + return (
13 + <div className="channel">
14 + <a href={channel.url} className="channel__url">
15 + {channel.thumbnail && <img className="channel__thumbnail" src={channel.thumbnail} alt=""></img>}
16 + {!channel.thumbnail && <PersonIcon className="channel__icon"/>}
17 + <div className="channel__box">
18 + <div className="channel__info">
19 + <div className="channel__name">{channel.name}</div>
20 + <div className="channel__game">{channel.game}</div>
21 + </div>
22 + <div className="channel__view">{channel.view}</div>
23 + </div>
24 + </a>
25 + {channel.isPlay && <PauseIcon className="pause__icon" onClick={props.onClick} />}
26 + {!channel.isPlay && <PlayArrowIcon className="play__icon" onClick={props.onClick} />}
27 + </div>
28 + );
29 +}
30 +
31 +export class ChannelList extends React.Component {
32 + constructor(props) {
33 + super(props);
34 +
35 + // data for test
36 + const test = [
37 + {"name": "name1", "view": 999, "game": "game1" ,"url": "https://www.twitch.tv", "thumbnail": "https://upload.wikimedia.org/wikipedia/commons/2/26/Twitch_logo.svg"},
38 + {"name": "name2", "view": 123124124, "game": "game2" , "url": "https://www.twitch.tv"},
39 + {"name": "name1", "view": 999, "game": "game1" ,"url": "https://www.twitch.tv", "thumbnail": "https://upload.wikimedia.org/wikipedia/commons/2/26/Twitch_logo.svg"},
40 + {"name": "name2", "view": 123124124, "game": "game2" , "url": "https://www.twitch.tv"},
41 + {"name": "name1", "view": 999, "game": "game1" ,"url": "https://www.twitch.tv", "thumbnail": "https://upload.wikimedia.org/wikipedia/commons/2/26/Twitch_logo.svg"},
42 + {"name": "name2", "view": 123124124, "game": "game2" , "url": "https://www.twitch.tv"},
43 + {"name": "name1", "view": 999, "game": "game1" ,"url": "https://www.twitch.tv", "thumbnail": "https://upload.wikimedia.org/wikipedia/commons/2/26/Twitch_logo.svg"},
44 + {"name": "name2", "view": 123124124, "game": "game2" , "url": "https://www.twitch.tv"},
45 + {"name": "name1", "view": 999, "game": "game1" ,"url": "https://www.twitch.tv", "thumbnail": "https://upload.wikimedia.org/wikipedia/commons/2/26/Twitch_logo.svg"},
46 + {"name": "name2", "view": 123124124, "game": "game2" , "url": "https://www.twitch.tv"},
47 + ]
48 +
49 + this.state = {
50 + channels: test,
51 + xisPlaying: null,
52 + }
53 + }
54 +
55 + changeState(channel, index){
56 + const channels = this.state.channels;
57 + // 버튼을 누른 채널이 재생중일때
58 + if (channel.isPlay === true) {
59 + channels[index].isPlay = false;
60 + this.setState({
61 + channels: channels,
62 + xisPlaying: null,
63 + });
64 + }
65 +
66 + // 버튼을 누른 채널이 재생중이 아닐 때
67 + else {
68 + // xisPlaying이 null이 아닐 때
69 + if (this.state.xisPlaying !== null) {
70 + // 기존에 재생되던 채널의 isPlay를 false로, 클릭 이벤트가 발생한 채널의 isPlay를 true로 변경
71 + channels[this.state.xisPlaying].isPlay = false;
72 + channels[index].isPlay = true;
73 + }
74 +
75 + // xisPlaying이 null일 때
76 + else {
77 + channels[index].isPlay = true;
78 + }
79 +
80 + this.setState({
81 + channels: channels,
82 + xisPlaying: index,
83 + });
84 + }
85 + }
86 +
87 + render() {
88 + return (
89 + <div className="channel__list">
90 + <div className="channel__list__title"> CHANNEL LIST</div>
91 + <ul>
92 + {this.state.channels.map((channel, index) => (
93 + <li key={index}>
94 + <Channel
95 + channel={channel}
96 + onClick={ () => this.changeState(channel, index)}
97 + />
98 + <hr />
99 + </li>
100 + ))}
101 + </ul>
102 + </div>
103 +
104 + );
105 + }
106 +}
...\ No newline at end of file ...\ No newline at end of file
1 +.login {
2 + display: grid;
3 + background-color: #0e0e10;
4 + height: 100vh;
5 + place-items: center;
6 +}
7 +
8 +.login > img {
9 + height: 300px;
10 +}
11 +
12 +.login > a {
13 + color: white;
14 + padding: 30px;
15 + border-radius: 100px;
16 + text-decoration: none;
17 + font-size: 30px;
18 + font-weight: bold;
19 + background-color: #9147ff;
20 +}
...\ No newline at end of file ...\ No newline at end of file
1 +import React from "react";
2 +import "./Login.css";
3 +
4 +function Login(){
5 + const OAuthUrl = ""; // oauth 인증용 url
6 + return (
7 + <div className="login">
8 + <img
9 + src="https://upload.wikimedia.org/wikipedia/commons/2/26/Twitch_logo.svg"
10 + alt=""
11 + />
12 + <a href={OAuthUrl}>Login to Twitch</a>
13 + </div>
14 + );
15 +}
16 +
17 +export default Login;
...\ No newline at end of file ...\ No newline at end of file
1 +body {
2 + margin: 0;
3 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 + sans-serif;
6 + -webkit-font-smoothing: antialiased;
7 + -moz-osx-font-smoothing: grayscale;
8 +}
9 +
10 +code {
11 + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 + monospace;
13 +}
1 +import React from 'react';
2 +import ReactDOM from 'react-dom';
3 +import './index.css';
4 +import App from './App';
5 +import reportWebVitals from './reportWebVitals';
6 +
7 +ReactDOM.render(
8 + <React.StrictMode>
9 + <App />
10 + </React.StrictMode>,
11 + document.getElementById('root')
12 +);
13 +
14 +// If you want to start measuring performance in your app, pass a function
15 +// to log results (for example: reportWebVitals(console.log))
16 +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 +reportWebVitals();
1 +const reportWebVitals = onPerfEntry => {
2 + if (onPerfEntry && onPerfEntry instanceof Function) {
3 + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 + getCLS(onPerfEntry);
5 + getFID(onPerfEntry);
6 + getFCP(onPerfEntry);
7 + getLCP(onPerfEntry);
8 + getTTFB(onPerfEntry);
9 + });
10 + }
11 +};
12 +
13 +export default reportWebVitals;
1 +// jest-dom adds custom jest matchers for asserting on DOM nodes.
2 +// allows you to do things like:
3 +// expect(element).toHaveTextContent(/react/i)
4 +// learn more: https://github.com/testing-library/jest-dom
5 +import '@testing-library/jest-dom';