Showing
24 changed files
with
365 additions
and
118 deletions
assets/js/videoPlayer.js
0 → 100644
File mode changed
assets/scss/config/utils.scss
0 → 100644
assets/scss/pages/userProfile.scss
0 → 100644
| 1 | +.user-profile { | ||
| 2 | + width: 100%; | ||
| 3 | + display: flex; | ||
| 4 | + align-items: center; | ||
| 5 | + flex-direction: column; | ||
| 6 | + .user-profile__header { | ||
| 7 | + display: flex; | ||
| 8 | + flex-direction: column; | ||
| 9 | + align-items: center; | ||
| 10 | + margin-bottom: 18px; | ||
| 11 | + .profile__username { | ||
| 12 | + font-size: 18px; | ||
| 13 | + margin-top: 15px; | ||
| 14 | + } | ||
| 15 | + } | ||
| 16 | + .user-profile__btns { | ||
| 17 | + display: flex; | ||
| 18 | + flex-direction: column; | ||
| 19 | + align-items: center; | ||
| 20 | + a:not(:last-child) { | ||
| 21 | + margin-bottom: 20px; | ||
| 22 | + } | ||
| 23 | + } | ||
| 24 | +} |
| ... | @@ -10,6 +10,11 @@ | ... | @@ -10,6 +10,11 @@ |
| 10 | width: 100px; | 10 | width: 100px; |
| 11 | margin-bottom: 25px; | 11 | margin-bottom: 25px; |
| 12 | } | 12 | } |
| 13 | + .video__author { | ||
| 14 | + a { | ||
| 15 | + color: #3498db; | ||
| 16 | + } | ||
| 17 | + } | ||
| 13 | .video__title, | 18 | .video__title, |
| 14 | .video__views, | 19 | .video__views, |
| 15 | .video__description { | 20 | .video__description { | ... | ... |
assets/scss/partials/videoPlayer.scss
0 → 100644
| 1 | +.videoPlayer { | ||
| 2 | + position: relative; | ||
| 3 | + &:hover { | ||
| 4 | + .videoPlayer__controls { | ||
| 5 | + opacity: 1; | ||
| 6 | + } | ||
| 7 | + } | ||
| 8 | + video { | ||
| 9 | + max-width: 100%; | ||
| 10 | + } | ||
| 11 | + .videoPlayer__controls { | ||
| 12 | + opacity: 0; | ||
| 13 | + transition: opacity 0.4s linear; | ||
| 14 | + color: white; | ||
| 15 | + position: absolute; | ||
| 16 | + z-index: 9; | ||
| 17 | + bottom: 5px; | ||
| 18 | + width: 100%; | ||
| 19 | + background-color: rgba(0, 0, 0, 0.5); | ||
| 20 | + padding: 10px; | ||
| 21 | + display: grid; | ||
| 22 | + grid-template-columns: repeat(3, 1fr); | ||
| 23 | + font-size: 16px; | ||
| 24 | + .videoPlayer__column:first-child { | ||
| 25 | + display: flex; | ||
| 26 | + align-items: center; | ||
| 27 | + span:first-child { | ||
| 28 | + margin-right: 10px; | ||
| 29 | + } | ||
| 30 | + } | ||
| 31 | + .videoPlayer__column:last-child { | ||
| 32 | + justify-self: end; | ||
| 33 | + } | ||
| 34 | + .videoPlayer__column:nth-child(2) { | ||
| 35 | + justify-self: center; | ||
| 36 | + } | ||
| 37 | + i { | ||
| 38 | + font-size: 25px; | ||
| 39 | + cursor: pointer; | ||
| 40 | + } | ||
| 41 | + } | ||
| 42 | +} |
| 1 | @import "config/_variables.scss"; | 1 | @import "config/_variables.scss"; |
| 2 | @import "config/reset.scss"; | 2 | @import "config/reset.scss"; |
| 3 | +@import "config/utils.scss"; | ||
| 3 | @import "main.scss"; | 4 | @import "main.scss"; |
| 4 | 5 | ||
| 5 | @import "partials/header.scss"; | 6 | @import "partials/header.scss"; |
| ... | @@ -7,6 +8,8 @@ | ... | @@ -7,6 +8,8 @@ |
| 7 | @import "partials/form.scss"; | 8 | @import "partials/form.scss"; |
| 8 | @import "partials/socialLogin.scss"; | 9 | @import "partials/socialLogin.scss"; |
| 9 | @import "partials/videoBlock.scss"; | 10 | @import "partials/videoBlock.scss"; |
| 11 | +@import "partials/videoPlayer.scss"; | ||
| 10 | 12 | ||
| 11 | @import "pages/home.scss"; | 13 | @import "pages/home.scss"; |
| 12 | @import "pages/videoDetail.scss"; | 14 | @import "pages/videoDetail.scss"; |
| 15 | +@import "pages/userProfile.scss"; | ... | ... |
| ... | @@ -120,7 +120,7 @@ export const logout = (req, res) => { | ... | @@ -120,7 +120,7 @@ export const logout = (req, res) => { |
| 120 | 120 | ||
| 121 | export const getMe = (req, res) => | 121 | export const getMe = (req, res) => |
| 122 | res.render("userDetail", { pageTitle: "User Detail", user: req.user }); | 122 | res.render("userDetail", { pageTitle: "User Detail", user: req.user }); |
| 123 | - | 123 | +// req.user -> 로그인된 유저 |
| 124 | // export const users = (req, res) => res.render("users", { pageTitle: "Users" }); | 124 | // export const users = (req, res) => res.render("users", { pageTitle: "Users" }); |
| 125 | 125 | ||
| 126 | export const userDetail = async (req, res) => { | 126 | export const userDetail = async (req, res) => { |
| ... | @@ -128,13 +128,50 @@ export const userDetail = async (req, res) => { | ... | @@ -128,13 +128,50 @@ export const userDetail = async (req, res) => { |
| 128 | params: { id }, | 128 | params: { id }, |
| 129 | } = req; // req로 부터 params의 id가져오기 | 129 | } = req; // req로 부터 params의 id가져오기 |
| 130 | try { | 130 | try { |
| 131 | - const user = await User.findById(id); | 131 | + const user = await User.findById(id).populate("videos"); |
| 132 | res.render("userDetail", { pageTitle: "User Detail", user }); | 132 | res.render("userDetail", { pageTitle: "User Detail", user }); |
| 133 | } catch (error) { | 133 | } catch (error) { |
| 134 | res.redirect(routes.home); | 134 | res.redirect(routes.home); |
| 135 | } | 135 | } |
| 136 | }; | 136 | }; |
| 137 | -export const editProfile = (req, res) => | 137 | +export const getEditProfile = (req, res) => |
| 138 | res.render("editProfile", { pageTitle: "Edit Profile" }); | 138 | res.render("editProfile", { pageTitle: "Edit Profile" }); |
| 139 | -export const changePassword = (req, res) => | 139 | + |
| 140 | +export const postEditProfile = async (req, res) => { | ||
| 141 | + const { | ||
| 142 | + body: { name, email }, | ||
| 143 | + file, | ||
| 144 | + } = req; | ||
| 145 | + try { | ||
| 146 | + // 로그인된 user의 id 가져오면 됨 | ||
| 147 | + await User.findByIdAndUpdate(req.user.id, { | ||
| 148 | + name, | ||
| 149 | + email, | ||
| 150 | + avatarUrl: file ? file.path : req.user.avatarUrl, | ||
| 151 | + }); | ||
| 152 | + res.redirect(routes.me); | ||
| 153 | + } catch (error) { | ||
| 154 | + res.redirect(`/users${routes.editProfile}`); | ||
| 155 | + } | ||
| 156 | +}; | ||
| 157 | + | ||
| 158 | +export const getChangePassword = (req, res) => | ||
| 140 | res.render("changePassword", { pageTitle: "Change Password" }); | 159 | res.render("changePassword", { pageTitle: "Change Password" }); |
| 160 | + | ||
| 161 | +export const postChangePassword = async (req, res) => { | ||
| 162 | + const { | ||
| 163 | + body: { oldPassword, newPassword, newPassword1 }, | ||
| 164 | + } = req; | ||
| 165 | + try { | ||
| 166 | + if (newPassword !== newPassword1) { | ||
| 167 | + res.status(400); | ||
| 168 | + res.redirect(`/users${routes.changePassword}`); | ||
| 169 | + return; | ||
| 170 | + } | ||
| 171 | + await req.user.changePassword(oldPassword, newPassword); | ||
| 172 | + res.redirect(routes.me); | ||
| 173 | + } catch (error) { | ||
| 174 | + res.status(400); | ||
| 175 | + res.redirect(`/users${routes.changePassword}`); | ||
| 176 | + } | ||
| 177 | +}; | ... | ... |
| ... | @@ -46,9 +46,12 @@ export const postUpload = async (req, res) => { | ... | @@ -46,9 +46,12 @@ export const postUpload = async (req, res) => { |
| 46 | fileUrl: path, | 46 | fileUrl: path, |
| 47 | title, | 47 | title, |
| 48 | description, | 48 | description, |
| 49 | + creator: req.user.id, | ||
| 49 | // 여기있는 fileUrl, title, description은 videoDB의 속성이다. | 50 | // 여기있는 fileUrl, title, description은 videoDB의 속성이다. |
| 50 | }); | 51 | }); |
| 51 | - console.log(newVideo); | 52 | + // console.log(newVideo); |
| 53 | + req.user.videos.push(newVideo.id); // user DB의 video atribute에 추가 | ||
| 54 | + req.user.save(); | ||
| 52 | res.redirect(routes.videoDetail(newVideo.id)); // id는 DB의 id | 55 | res.redirect(routes.videoDetail(newVideo.id)); // id는 DB의 id |
| 53 | // id는 mongoDB가 랜덤하게 만들어준다. | 56 | // id는 mongoDB가 랜덤하게 만들어준다. |
| 54 | }; | 57 | }; |
| ... | @@ -59,7 +62,7 @@ export const videoDetail = async (req, res) => { | ... | @@ -59,7 +62,7 @@ export const videoDetail = async (req, res) => { |
| 59 | params: { id }, | 62 | params: { id }, |
| 60 | } = req; | 63 | } = req; |
| 61 | try { | 64 | try { |
| 62 | - const video = await Video.findById(id); | 65 | + const video = await Video.findById(id).populate("creator"); |
| 63 | res.render("videoDetail", { pageTitle: video.title, video }); | 66 | res.render("videoDetail", { pageTitle: video.title, video }); |
| 64 | } catch (error) { | 67 | } catch (error) { |
| 65 | res.redirect(routes.home); | 68 | res.redirect(routes.home); |
| ... | @@ -72,7 +75,11 @@ export const getEditVideo = async (req, res) => { | ... | @@ -72,7 +75,11 @@ export const getEditVideo = async (req, res) => { |
| 72 | try { | 75 | try { |
| 73 | const video = await Video.findById(id); | 76 | const video = await Video.findById(id); |
| 74 | // video를 받아서 render로 통해 템플릿으로 던져준다, | 77 | // video를 받아서 render로 통해 템플릿으로 던져준다, |
| 75 | - res.render("editVideo", { pageTitle: `Edit ${video.title}`, video }); | 78 | + if (String(video.creator) !== req.user.id) { |
| 79 | + throw Error(); | ||
| 80 | + } else { | ||
| 81 | + res.render("editVideo", { pageTitle: `Edit ${video.title}`, video }); | ||
| 82 | + } | ||
| 76 | // rendering하는 순간 템플릿에선 video의 title과 description을 던져준다. | 83 | // rendering하는 순간 템플릿에선 video의 title과 description을 던져준다. |
| 77 | } catch (error) { | 84 | } catch (error) { |
| 78 | res.redirect(routes.home); | 85 | res.redirect(routes.home); |
| ... | @@ -99,7 +106,13 @@ export const deleteVideo = async (req, res) => { | ... | @@ -99,7 +106,13 @@ export const deleteVideo = async (req, res) => { |
| 99 | params: { id }, | 106 | params: { id }, |
| 100 | } = req; | 107 | } = req; |
| 101 | try { | 108 | try { |
| 102 | - await Video.findOneAndRemove({ _id: id }); | 109 | + const video = await Video.findById(id); |
| 110 | + // video를 받아서 render로 통해 템플릿으로 던져준다, | ||
| 111 | + if (String(video.creator) !== req.user.id) { | ||
| 112 | + throw Error(); | ||
| 113 | + } else { | ||
| 114 | + await Video.findOneAndRemove({ _id: id }); | ||
| 115 | + } | ||
| 103 | } catch (error) { | 116 | } catch (error) { |
| 104 | console.log(error); | 117 | console.log(error); |
| 105 | } | 118 | } | ... | ... |
| ... | @@ -2,6 +2,7 @@ import multer from "multer"; | ... | @@ -2,6 +2,7 @@ import multer from "multer"; |
| 2 | import routes from "./routes"; | 2 | import routes from "./routes"; |
| 3 | 3 | ||
| 4 | const multerVideo = multer({ dest: "uploads/videos/" }); | 4 | const multerVideo = multer({ dest: "uploads/videos/" }); |
| 5 | +const multerAvatar = multer({ dest: "uploads/avatars/" }); | ||
| 5 | 6 | ||
| 6 | export const localsMiddleware = (req, res, next) => { | 7 | export const localsMiddleware = (req, res, next) => { |
| 7 | res.locals.siteName = "my Youtube"; | 8 | res.locals.siteName = "my Youtube"; |
| ... | @@ -26,3 +27,4 @@ export const onlyPrivate = (req, res, next) => { | ... | @@ -26,3 +27,4 @@ export const onlyPrivate = (req, res, next) => { |
| 26 | }; | 27 | }; |
| 27 | export const uploadVideo = multerVideo.single("videoFile"); | 28 | export const uploadVideo = multerVideo.single("videoFile"); |
| 28 | // single에 들어간 videoFile은 upload.pug의 file 부분 input name | 29 | // single에 들어간 videoFile은 upload.pug의 file 부분 input name |
| 30 | +export const uploadAvatar = multerAvatar.single("avatar"); | ... | ... |
| ... | @@ -10,6 +10,10 @@ const CommentSchema = new mongoose.Schema({ | ... | @@ -10,6 +10,10 @@ const CommentSchema = new mongoose.Schema({ |
| 10 | type: Date, | 10 | type: Date, |
| 11 | default: Date.now, | 11 | default: Date.now, |
| 12 | }, | 12 | }, |
| 13 | + creator: { | ||
| 14 | + type: mongoose.Schema.Types.ObjectId, | ||
| 15 | + ref: "User", | ||
| 16 | + }, | ||
| 13 | /* | 17 | /* |
| 14 | 18 | ||
| 15 | video: { //video와 comment를 연결하는 방법 #2 | 19 | video: { //video와 comment를 연결하는 방법 #2 | ... | ... |
| ... | @@ -7,6 +7,18 @@ const UserSchema = new mongoose.Schema({ | ... | @@ -7,6 +7,18 @@ const UserSchema = new mongoose.Schema({ |
| 7 | avatarUrl: String, | 7 | avatarUrl: String, |
| 8 | facebookId: Number, | 8 | facebookId: Number, |
| 9 | githubId: Number, | 9 | githubId: Number, |
| 10 | + comments: [ | ||
| 11 | + { | ||
| 12 | + type: mongoose.Schema.Types.ObjectId, // 그 다음 어느 model에서 온 id인지 알려줘야 한다. | ||
| 13 | + ref: "Comment", | ||
| 14 | + }, | ||
| 15 | + ], | ||
| 16 | + videos: [ | ||
| 17 | + { | ||
| 18 | + type: mongoose.Schema.Types.ObjectId, // 그 다음 어느 model에서 온 id인지 알려줘야 한다. | ||
| 19 | + ref: "Video", | ||
| 20 | + }, | ||
| 21 | + ], | ||
| 10 | }); | 22 | }); |
| 11 | // 이 상태에서 새로운 스키마를 추가한다. | 23 | // 이 상태에서 새로운 스키마를 추가한다. |
| 12 | // passportLocalMongoose는 configuration object가 필요하다. | 24 | // passportLocalMongoose는 configuration object가 필요하다. | ... | ... |
| ... | @@ -27,6 +27,10 @@ const VideoSchema = new mongoose.Schema({ | ... | @@ -27,6 +27,10 @@ const VideoSchema = new mongoose.Schema({ |
| 27 | ref: "Comment", | 27 | ref: "Comment", |
| 28 | }, | 28 | }, |
| 29 | ], | 29 | ], |
| 30 | + creator: { | ||
| 31 | + type: mongoose.Schema.Types.ObjectId, | ||
| 32 | + ref: "User", | ||
| 33 | + }, | ||
| 30 | }); | 34 | }); |
| 31 | // 이제 이 스키마를 이용하여 model을 만들어준다. | 35 | // 이제 이 스키마를 이용하여 model을 만들어준다. |
| 32 | // 모델의 이름은 "Video" | 36 | // 모델의 이름은 "Video" | ... | ... |
| ... | @@ -4,7 +4,7 @@ | ... | @@ -4,7 +4,7 @@ |
| 4 | "description": "make Youtube Website", | 4 | "description": "make Youtube Website", |
| 5 | "main": "app.js", | 5 | "main": "app.js", |
| 6 | "scripts": { | 6 | "scripts": { |
| 7 | - "dev:server": "nodemon --exec babel-node init.js --delay 2 --ignore 'scss'", | 7 | + "dev:server": "nodemon --exec babel-node init.js --delay 2 --ignore '.scss' --ignore 'static'", |
| 8 | "dev:assets": "WEBPACK_ENV=development webpack -w", | 8 | "dev:assets": "WEBPACK_ENV=development webpack -w", |
| 9 | "build:assets": "WEBPACK_ENV=production webpack", | 9 | "build:assets": "WEBPACK_ENV=production webpack", |
| 10 | "tunnel": "ngrok http 80" | 10 | "tunnel": "ngrok http 80" | ... | ... |
| ... | @@ -2,15 +2,20 @@ import express from "express"; | ... | @@ -2,15 +2,20 @@ import express from "express"; |
| 2 | import routes from "../routes"; | 2 | import routes from "../routes"; |
| 3 | import { | 3 | import { |
| 4 | userDetail, | 4 | userDetail, |
| 5 | - editProfile, | 5 | + getEditProfile, |
| 6 | - changePassword, | 6 | + postEditProfile, |
| 7 | + getChangePassword, | ||
| 8 | + postChangePassword, | ||
| 7 | } from "../controllers/userController"; | 9 | } from "../controllers/userController"; |
| 8 | -import { onlyPrivate } from "../middlewares"; | 10 | +import { onlyPrivate, uploadAvatar } from "../middlewares"; |
| 9 | 11 | ||
| 10 | const userRouter = express.Router(); | 12 | const userRouter = express.Router(); |
| 11 | 13 | ||
| 12 | -userRouter.get(routes.editProfile, onlyPrivate, editProfile); | 14 | +userRouter.get(routes.editProfile, onlyPrivate, getEditProfile); |
| 13 | -userRouter.get(routes.changePassword, onlyPrivate, changePassword); | 15 | +userRouter.post(routes.editProfile, onlyPrivate, uploadAvatar, postEditProfile); |
| 16 | + | ||
| 17 | +userRouter.get(routes.changePassword, onlyPrivate, getChangePassword); | ||
| 18 | +userRouter.post(routes.changePassword, onlyPrivate, postChangePassword); | ||
| 14 | userRouter.get(routes.userDetail(), userDetail); | 19 | userRouter.get(routes.userDetail(), userDetail); |
| 15 | 20 | ||
| 16 | export default userRouter; | 21 | export default userRouter; | ... | ... |
| ... | @@ -4,9 +4,9 @@ import { | ... | @@ -4,9 +4,9 @@ import { |
| 4 | getUpload, | 4 | getUpload, |
| 5 | postUpload, | 5 | postUpload, |
| 6 | videoDetail, | 6 | videoDetail, |
| 7 | + deleteVideo, | ||
| 7 | getEditVideo, | 8 | getEditVideo, |
| 8 | postEditVideo, | 9 | postEditVideo, |
| 9 | - deleteVideo, | ||
| 10 | } from "../controllers/videoController"; | 10 | } from "../controllers/videoController"; |
| 11 | import { uploadVideo, onlyPrivate } from "../middlewares"; | 11 | import { uploadVideo, onlyPrivate } from "../middlewares"; |
| 12 | // export const videoRouter = express.Router(); 이렇게하면 이 변수만 export하게 된다. | 12 | // export const videoRouter = express.Router(); 이렇게하면 이 변수만 export하게 된다. | ... | ... |
| ... | @@ -94,18 +94,29 @@ | ... | @@ -94,18 +94,29 @@ |
| 94 | /***/ (function(module, __webpack_exports__, __webpack_require__) { | 94 | /***/ (function(module, __webpack_exports__, __webpack_require__) { |
| 95 | 95 | ||
| 96 | "use strict"; | 96 | "use strict"; |
| 97 | -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _scss_style_scss__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../scss/style.scss */ \"./assets/scss/style.scss\");\n/* harmony import */ var _scss_style_scss__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_scss_style_scss__WEBPACK_IMPORTED_MODULE_0__);\n\n\nconst something = async () => {\n console.log(\"something\");\n};\n\n\n//# sourceURL=webpack:///./assets/js/main.js?"); | 97 | +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _scss_styles_scss__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../scss/styles.scss */ \"./assets/scss/styles.scss\");\n/* harmony import */ var _scss_styles_scss__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_scss_styles_scss__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _videoPlayer__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./videoPlayer */ \"./assets/js/videoPlayer.js\");\n/* harmony import */ var _videoPlayer__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_videoPlayer__WEBPACK_IMPORTED_MODULE_1__);\n\n\n\n\n//# sourceURL=webpack:///./assets/js/main.js?"); |
| 98 | 98 | ||
| 99 | /***/ }), | 99 | /***/ }), |
| 100 | 100 | ||
| 101 | -/***/ "./assets/scss/style.scss": | 101 | +/***/ "./assets/js/videoPlayer.js": |
| 102 | -/*!********************************!*\ | 102 | +/*!**********************************!*\ |
| 103 | - !*** ./assets/scss/style.scss ***! | 103 | + !*** ./assets/js/videoPlayer.js ***! |
| 104 | - \********************************/ | 104 | + \**********************************/ |
| 105 | /*! no static exports found */ | 105 | /*! no static exports found */ |
| 106 | /***/ (function(module, exports) { | 106 | /***/ (function(module, exports) { |
| 107 | 107 | ||
| 108 | -eval("// removed by extract-text-webpack-plugin\n\n//# sourceURL=webpack:///./assets/scss/style.scss?"); | 108 | +eval("\n\n//# sourceURL=webpack:///./assets/js/videoPlayer.js?"); |
| 109 | + | ||
| 110 | +/***/ }), | ||
| 111 | + | ||
| 112 | +/***/ "./assets/scss/styles.scss": | ||
| 113 | +/*!*********************************!*\ | ||
| 114 | + !*** ./assets/scss/styles.scss ***! | ||
| 115 | + \*********************************/ | ||
| 116 | +/*! no static exports found */ | ||
| 117 | +/***/ (function(module, exports) { | ||
| 118 | + | ||
| 119 | +eval("// removed by extract-text-webpack-plugin\n\n//# sourceURL=webpack:///./assets/scss/styles.scss?"); | ||
| 109 | 120 | ||
| 110 | /***/ }), | 121 | /***/ }), |
| 111 | 122 | ... | ... |
| ... | @@ -140,6 +140,13 @@ input { | ... | @@ -140,6 +140,13 @@ input { |
| 140 | input:focus, input:active { | 140 | input:focus, input:active { |
| 141 | outline: none; } | 141 | outline: none; } |
| 142 | 142 | ||
| 143 | +.u-avatar { | ||
| 144 | + height: 80px; | ||
| 145 | + background-color: #ea232c; | ||
| 146 | + -webkit-border-radius: 50%; | ||
| 147 | + -moz-border-radius: 50%; | ||
| 148 | + border-radius: 50%; } | ||
| 149 | + | ||
| 143 | html, | 150 | html, |
| 144 | body { | 151 | body { |
| 145 | height: 100%; } | 152 | height: 100%; } |
| ... | @@ -413,6 +420,49 @@ input[type="submit"] { | ... | @@ -413,6 +420,49 @@ input[type="submit"] { |
| 413 | font-weight: 300; | 420 | font-weight: 300; |
| 414 | margin-bottom: 10px; } | 421 | margin-bottom: 10px; } |
| 415 | 422 | ||
| 423 | +.videoPlayer { | ||
| 424 | + position: relative; } | ||
| 425 | + .videoPlayer:hover .videoPlayer__controls { | ||
| 426 | + opacity: 1; } | ||
| 427 | + .videoPlayer video { | ||
| 428 | + max-width: 100%; } | ||
| 429 | + .videoPlayer .videoPlayer__controls { | ||
| 430 | + opacity: 0; | ||
| 431 | + -webkit-transition: opacity 0.4s linear; | ||
| 432 | + -o-transition: opacity 0.4s linear; | ||
| 433 | + -moz-transition: opacity 0.4s linear; | ||
| 434 | + transition: opacity 0.4s linear; | ||
| 435 | + color: white; | ||
| 436 | + position: absolute; | ||
| 437 | + z-index: 9; | ||
| 438 | + bottom: 5px; | ||
| 439 | + width: 100%; | ||
| 440 | + background-color: rgba(0, 0, 0, 0.5); | ||
| 441 | + padding: 10px; | ||
| 442 | + display: grid; | ||
| 443 | + grid-template-columns: repeat(3, 1fr); | ||
| 444 | + font-size: 16px; } | ||
| 445 | + .videoPlayer .videoPlayer__controls .videoPlayer__column:first-child { | ||
| 446 | + display: -webkit-box; | ||
| 447 | + display: -webkit-flex; | ||
| 448 | + display: -moz-box; | ||
| 449 | + display: -ms-flexbox; | ||
| 450 | + display: flex; | ||
| 451 | + -webkit-box-align: center; | ||
| 452 | + -webkit-align-items: center; | ||
| 453 | + -moz-box-align: center; | ||
| 454 | + -ms-flex-align: center; | ||
| 455 | + align-items: center; } | ||
| 456 | + .videoPlayer .videoPlayer__controls .videoPlayer__column:first-child span:first-child { | ||
| 457 | + margin-right: 10px; } | ||
| 458 | + .videoPlayer .videoPlayer__controls .videoPlayer__column:last-child { | ||
| 459 | + justify-self: end; } | ||
| 460 | + .videoPlayer .videoPlayer__controls .videoPlayer__column:nth-child(2) { | ||
| 461 | + justify-self: center; } | ||
| 462 | + .videoPlayer .videoPlayer__controls i { | ||
| 463 | + font-size: 25px; | ||
| 464 | + cursor: pointer; } | ||
| 465 | + | ||
| 416 | .home-videos { | 466 | .home-videos { |
| 417 | display: grid; | 467 | display: grid; |
| 418 | grid-template-columns: repeat(6, minmax(150px, 1fr)); | 468 | grid-template-columns: repeat(6, minmax(150px, 1fr)); |
| ... | @@ -450,6 +500,8 @@ input[type="submit"] { | ... | @@ -450,6 +500,8 @@ input[type="submit"] { |
| 450 | .video-detail__container .video__info button { | 500 | .video-detail__container .video__info button { |
| 451 | width: 100px; | 501 | width: 100px; |
| 452 | margin-bottom: 25px; } | 502 | margin-bottom: 25px; } |
| 503 | + .video-detail__container .video__info .video__author a { | ||
| 504 | + color: #3498db; } | ||
| 453 | .video-detail__container .video__info .video__title, | 505 | .video-detail__container .video__info .video__title, |
| 454 | .video-detail__container .video__info .video__views, | 506 | .video-detail__container .video__info .video__views, |
| 455 | .video-detail__container .video__info .video__description { | 507 | .video-detail__container .video__info .video__description { |
| ... | @@ -466,3 +518,65 @@ input[type="submit"] { | ... | @@ -466,3 +518,65 @@ input[type="submit"] { |
| 466 | margin-top: 25px; } | 518 | margin-top: 25px; } |
| 467 | .video-detail__container .video__comments .video__comment-number { | 519 | .video-detail__container .video__comments .video__comment-number { |
| 468 | font-size: 18px; } | 520 | font-size: 18px; } |
| 521 | + | ||
| 522 | +.user-profile { | ||
| 523 | + width: 100%; | ||
| 524 | + display: -webkit-box; | ||
| 525 | + display: -webkit-flex; | ||
| 526 | + display: -moz-box; | ||
| 527 | + display: -ms-flexbox; | ||
| 528 | + display: flex; | ||
| 529 | + -webkit-box-align: center; | ||
| 530 | + -webkit-align-items: center; | ||
| 531 | + -moz-box-align: center; | ||
| 532 | + -ms-flex-align: center; | ||
| 533 | + align-items: center; | ||
| 534 | + -webkit-box-orient: vertical; | ||
| 535 | + -webkit-box-direction: normal; | ||
| 536 | + -webkit-flex-direction: column; | ||
| 537 | + -moz-box-orient: vertical; | ||
| 538 | + -moz-box-direction: normal; | ||
| 539 | + -ms-flex-direction: column; | ||
| 540 | + flex-direction: column; } | ||
| 541 | + .user-profile .user-profile__header { | ||
| 542 | + display: -webkit-box; | ||
| 543 | + display: -webkit-flex; | ||
| 544 | + display: -moz-box; | ||
| 545 | + display: -ms-flexbox; | ||
| 546 | + display: flex; | ||
| 547 | + -webkit-box-orient: vertical; | ||
| 548 | + -webkit-box-direction: normal; | ||
| 549 | + -webkit-flex-direction: column; | ||
| 550 | + -moz-box-orient: vertical; | ||
| 551 | + -moz-box-direction: normal; | ||
| 552 | + -ms-flex-direction: column; | ||
| 553 | + flex-direction: column; | ||
| 554 | + -webkit-box-align: center; | ||
| 555 | + -webkit-align-items: center; | ||
| 556 | + -moz-box-align: center; | ||
| 557 | + -ms-flex-align: center; | ||
| 558 | + align-items: center; | ||
| 559 | + margin-bottom: 18px; } | ||
| 560 | + .user-profile .user-profile__header .profile__username { | ||
| 561 | + font-size: 18px; | ||
| 562 | + margin-top: 15px; } | ||
| 563 | + .user-profile .user-profile__btns { | ||
| 564 | + display: -webkit-box; | ||
| 565 | + display: -webkit-flex; | ||
| 566 | + display: -moz-box; | ||
| 567 | + display: -ms-flexbox; | ||
| 568 | + display: flex; | ||
| 569 | + -webkit-box-orient: vertical; | ||
| 570 | + -webkit-box-direction: normal; | ||
| 571 | + -webkit-flex-direction: column; | ||
| 572 | + -moz-box-orient: vertical; | ||
| 573 | + -moz-box-direction: normal; | ||
| 574 | + -ms-flex-direction: column; | ||
| 575 | + flex-direction: column; | ||
| 576 | + -webkit-box-align: center; | ||
| 577 | + -webkit-align-items: center; | ||
| 578 | + -moz-box-align: center; | ||
| 579 | + -ms-flex-align: center; | ||
| 580 | + align-items: center; } | ||
| 581 | + .user-profile .user-profile__btns a:not(:last-child) { | ||
| 582 | + margin-bottom: 20px; } | ... | ... |
| ... | @@ -3,7 +3,7 @@ extends layouts/main | ... | @@ -3,7 +3,7 @@ extends layouts/main |
| 3 | block content | 3 | block content |
| 4 | .form-container | 4 | .form-container |
| 5 | form(action=`/users${routes.changePassword}`, method="post") | 5 | form(action=`/users${routes.changePassword}`, method="post") |
| 6 | - input(type="password", name="oldPasswod", placeholder="Current Password") | 6 | + input(type="password", name="oldPassword", placeholder="Current Password") |
| 7 | input(type="password", name="newPassword", placeholder="New Password") | 7 | input(type="password", name="newPassword", placeholder="New Password") |
| 8 | input(type="password", name="newPassword1", placeholder="Verify New Password") | 8 | input(type="password", name="newPassword1", placeholder="Verify New Password") |
| 9 | input(type="submit", value="Change Password") | 9 | input(type="submit", value="Change Password") |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -2,12 +2,11 @@ extends layouts/main | ... | @@ -2,12 +2,11 @@ extends layouts/main |
| 2 | 2 | ||
| 3 | block content | 3 | block content |
| 4 | .form-container | 4 | .form-container |
| 5 | - form(action=routes.editProfile, method="post") | 5 | + form(action=`/users${routes.editProfile}`, method="post", enctype="multipart/form-data") |
| 6 | .fileUpload | 6 | .fileUpload |
| 7 | label(for="avatar") Avatar | 7 | label(for="avatar") Avatar |
| 8 | - input(type="file", id="avatar", name="avatar") | 8 | + input(type="file", id="avatar", name="avatar", accept="image/*") |
| 9 | - input(type="text", placeholder="Name", name="name") | 9 | + input(type="text", placeholder="Name", name="name", value=loggedUser.name) |
| 10 | - input(type="email", placeholder="Email", name="email") | 10 | + input(type="email", placeholder="Email", name="email", value=loggedUser.email) |
| 11 | input(type="submit", value="Update Profile") | 11 | input(type="submit", value="Update Profile") |
| 12 | - a.form-container__link(href=`/users${routes.changePassword}`) | ||
| 13 | - button Change Password | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 12 | + | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -9,70 +9,4 @@ block content | ... | @@ -9,70 +9,4 @@ block content |
| 9 | title : item.title, | 9 | title : item.title, |
| 10 | views: item.views, | 10 | views: item.views, |
| 11 | videoFile:item.fileUrl | 11 | videoFile:item.fileUrl |
| 12 | - }) | 12 | + }) |
| 13 | - +videoBlock({ | ||
| 14 | - id:item.id, | ||
| 15 | - title:item.title, | ||
| 16 | - views:item.views, | ||
| 17 | - videoFile:item.fileUrl | ||
| 18 | - }) | ||
| 19 | - +videoBlock({ | ||
| 20 | - id:item.id, | ||
| 21 | - title:item.title, | ||
| 22 | - views:item.views, | ||
| 23 | - videoFile:item.fileUrl | ||
| 24 | - }) | ||
| 25 | - +videoBlock({ | ||
| 26 | - id:item.id, | ||
| 27 | - title:item.title, | ||
| 28 | - views:item.views, | ||
| 29 | - videoFile:item.fileUrl | ||
| 30 | - }) | ||
| 31 | - +videoBlock({ | ||
| 32 | - id:item.id, | ||
| 33 | - title:item.title, | ||
| 34 | - views:item.views, | ||
| 35 | - videoFile:item.fileUrl | ||
| 36 | - }) | ||
| 37 | - +videoBlock({ | ||
| 38 | - id:item.id, | ||
| 39 | - title:item.title, | ||
| 40 | - views:item.views, | ||
| 41 | - videoFile:item.fileUrl | ||
| 42 | - }) | ||
| 43 | - +videoBlock({ | ||
| 44 | - id:item.id, | ||
| 45 | - title:item.title, | ||
| 46 | - views:item.views, | ||
| 47 | - videoFile:item.fileUrl | ||
| 48 | - }) | ||
| 49 | - +videoBlock({ | ||
| 50 | - id:item.id, | ||
| 51 | - title:item.title, | ||
| 52 | - views:item.views, | ||
| 53 | - videoFile:item.fileUrl | ||
| 54 | - }) | ||
| 55 | - +videoBlock({ | ||
| 56 | - id:item.id, | ||
| 57 | - title:item.title, | ||
| 58 | - views:item.views, | ||
| 59 | - videoFile:item.fileUrl | ||
| 60 | - }) | ||
| 61 | - +videoBlock({ | ||
| 62 | - id:item.id, | ||
| 63 | - title:item.title, | ||
| 64 | - views:item.views, | ||
| 65 | - videoFile:item.fileUrl | ||
| 66 | - }) | ||
| 67 | - +videoBlock({ | ||
| 68 | - id:item.id, | ||
| 69 | - title:item.title, | ||
| 70 | - views:item.views, | ||
| 71 | - videoFile:item.fileUrl | ||
| 72 | - }) | ||
| 73 | - +videoBlock({ | ||
| 74 | - id:item.id, | ||
| 75 | - title:item.title, | ||
| 76 | - views:item.views, | ||
| 77 | - videoFile:item.fileUrl | ||
| 78 | - }) | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
views/mixins/videoPlayer.pug
0 → 100644
| 1 | +mixin videoPlayer(video={}) | ||
| 2 | + .videoPlayer | ||
| 3 | + video(src=`/${video.src}`) | ||
| 4 | + .videoPlayer__controls | ||
| 5 | + .videoPlayer__column | ||
| 6 | + span | ||
| 7 | + i.fas.fa-volume-up | ||
| 8 | + span | ||
| 9 | + |00:00 / 10:00 | ||
| 10 | + .videoPlayer__column | ||
| 11 | + span | ||
| 12 | + i.fas.fa-play | ||
| 13 | + .videoPlayer__column | ||
| 14 | + span | ||
| 15 | + i.fas.fa-expand | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | extends layouts/main | 1 | extends layouts/main |
| 2 | +include mixins/videoBlock | ||
| 2 | 3 | ||
| 3 | block content | 4 | block content |
| 4 | .user-profile | 5 | .user-profile |
| 5 | .user-profile__header | 6 | .user-profile__header |
| 6 | - img.avatar(src=user.avatarUrl) | ||
| 7 | - h4.profile__username=user.name | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 7 | + img.u-avatar(src=user.avatarUrl) | ||
| 8 | + h4.profile__username=user.name | ||
| 9 | + if user.id === loggedUser.id | ||
| 10 | + .user-profile__btns | ||
| 11 | + a(href=`/users${routes.editProfile}`) | ||
| 12 | + button 🖌 Edit Profile | ||
| 13 | + a(href=`/users${routes.changePassword}`) | ||
| 14 | + button 🔒 Change Password | ||
| 15 | + .home-videos | ||
| 16 | + each item in user.videos | ||
| 17 | + +videoBlock({ | ||
| 18 | + id: item.id, | ||
| 19 | + title : item.title, | ||
| 20 | + views: item.views, | ||
| 21 | + videoFile:item.fileUrl | ||
| 22 | + }) | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | extends layouts/main | 1 | extends layouts/main |
| 2 | +include mixins/videoPlayer | ||
| 2 | 3 | ||
| 3 | block content | 4 | block content |
| 4 | - .video__player | 5 | + .video-detail__container |
| 5 | - video(src=`/${video.fileUrl}`) | 6 | + +videoPlayer({ |
| 6 | - .video__info | 7 | + src:video.fileUrl |
| 7 | - a(href=routes.editVideo(video.id)) | 8 | + }) |
| 8 | - button Edit video | 9 | + .video__info |
| 9 | - h5.video__title=video.title | 10 | + if loggedUser && video.creator.id === loggedUser.id |
| 10 | - span.video__views=video.views | 11 | + a(href=routes.editVideo(video.id)) |
| 11 | - p.video__description=video.description | 12 | + button Edit video |
| 12 | - if video.views === 1 | 13 | + h5.video__title=video.title |
| 14 | + p.video__description=video.description | ||
| 15 | + if video.views === 1 | ||
| 13 | span.video__views 1 view | 16 | span.video__views 1 view |
| 14 | - else | ||
| 15 | - span.video__views #{video.views} views | ||
| 16 | - .video__comment | ||
| 17 | - if video.comments.length === 1 | ||
| 18 | - span.video__comment-number 1 comment | ||
| 19 | - else | ||
| 20 | - span.video__comment-number #{video.comments.length} comments | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 17 | + else | ||
| 18 | + span.video__views #{video.views} views | ||
| 19 | + .video__author | ||
| 20 | + |Uploaded by | ||
| 21 | + a(href=routes.userDetail(video.creator.id))=video.creator.name | ||
| 22 | + .video__comment | ||
| 23 | + if video.comments.length === 1 | ||
| 24 | + span.video__comment-number 1 comment | ||
| 25 | + else | ||
| 26 | + span.video__comment-number #{video.comments.length} comments | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
-
Please register or login to post a comment