From 155fee34cba8f370e9bee8263e63a986da59c570 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Wed, 16 Mar 2022 15:22:18 +0900 Subject: [PATCH 01/37] =?UTF-8?q?chore:=20eslint=20=EC=84=A4=EC=A0=95,=20C?= =?UTF-8?q?RLF=20->=20LF=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.json | 5 ++++- package.json | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 48b77563c..42ad3ce90 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -13,6 +13,7 @@ "sourceType": "module" }, "rules": { + "no-new": 0, "no-var": "error", "max-depth": ["error", 2], "no-console": "warn", @@ -21,6 +22,8 @@ "no-constant-condition": 0, "no-unused-private-class-members": 0, "lines-between-class-members": 0, - "no-useless-escape": 0 + "no-useless-escape": 0, + "import/no-unresolved": 0, + "import/extensions": 0 } } diff --git a/package.json b/package.json index bb088b4a4..abdfbc39a 100644 --- a/package.json +++ b/package.json @@ -15,15 +15,15 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/kkojae91/javascript-youtube-classroom.git" + "url": "git+https://github.com/usageness/javascript-youtube-classroom.git" }, "keywords": [], "author": "", "license": "ISC", "bugs": { - "url": "https://github.com/kkojae91/javascript-youtube-classroom/issues" + "url": "https://github.com/usageness/javascript-youtube-classroom/issues" }, - "homepage": "https://github.com/kkojae91/javascript-youtube-classroom#readme", + "homepage": "https://usageness.github.io/javascript-youtube-classroom/", "devDependencies": { "@babel/core": "^7.17.5", "@babel/plugin-transform-runtime": "^7.17.0", From 2b83fef3d9f25207b4a9ae2567d44eeade6df020 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Wed, 16 Mar 2022 15:49:43 +0900 Subject: [PATCH 02/37] =?UTF-8?q?docs:=20todo=20list=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b4241f899..6fde341f0 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@

### 데모 페이지 + [데모 페이지](https://usageness.github.io/javascript-youtube-classroom/) --- @@ -20,6 +21,8 @@ --- +### Step 1 + - [x] 메인 화면에서 검색 버튼을 누르면 검색 모달창이 나타난다. - [x] 유튜브 검색 API를 사용해 내가 보고 싶은 영상들을 검색할 수 있다. - 엔터키를 눌러 검색할 수 있다. @@ -34,19 +37,33 @@ - 저장 가능한 최대 동영상의 갯수는 100개이다. - [x] 이미 저장된 영상이라면 저장 버튼이 보이지 않도록 한다. +### Step 2 + +- [ ] 가장 처음에는 저장된 영상이 없으므로, 비어있다는 것을 사용자에게 알려주는 상태를 보여준다. +- [ ] 이후 페이지를 방문했을 때 기본 메인 화면은 내가 볼 영상들의 리스트를 보여준다. +- [ ] 영상 카드의 이모지 버튼을 클릭하여 아래와 같은 상태 변경이 가능해야 한다. + - ✅ 본 영상으로 체크 + - 🗑️ 버튼으로 저장된 리스트에서 삭제할 수 있다. (삭제 시 사용자에게 정말 삭제할 것인지 물어봅니다.) +- [ ] 본 영상, 볼 영상 버튼을 눌러 필터링 할 수 있다. +- [ ] 반응형 웹: 유저가 사용하는 디바이스의 가로 길이에 따라 검색결과의 row 당 column 갯수를 변경한다. + - 1280px 이상: 4개 + - 960px이상~1280px 미만: 3개 + - 600px이상~960px 미만: 2개 + - 600px 미만: 1개 + ## 테스트 요구사항 --- - [x] 단위 테스트를 Jest로 작성한다. - [x] E2E 테스트를 Cypress로 작성한다. + - 검증이 필요하다고 생각되는 플로우를 1가지 설정하고 이에 대한 검증을 E2E 테스트로 진행한다. ## 배포 --- - [x] 실행 가능한 페이지에 접근할 수 있도록 github page 기능을 이용하고, 해당 링크를 PR과 README에 작성한다. -- API key를 public repo에 올리지 않은 채로 데모 페이지를 배포하려면, 별도의 설정이 추가로 필요합니다. ## 📝 License From 0714f78d6f99158dc37c11862182108b0da68339 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Wed, 16 Mar 2022 19:29:08 +0900 Subject: [PATCH 03/37] =?UTF-8?q?refactor:=20=EC=83=81=EC=88=98=EB=A5=BC?= =?UTF-8?q?=20=EC=9D=B4=EC=9A=A9=ED=95=98=EC=97=AC=20mockdata=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20=EC=97=AC=EB=B6=80=20=EA=B2=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - constants.js 의 DEVELOP_MODE 에 boolean 값을 주어 mockdata 사용 여부 조정 가능 --- src/js/YoutubeApp.js | 16 ---------------- src/js/api/getSearchResult.js | 17 ++++++++++++----- src/js/constants/constants.js | 2 ++ 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/js/YoutubeApp.js b/src/js/YoutubeApp.js index 69783b762..6d9578817 100644 --- a/src/js/YoutubeApp.js +++ b/src/js/YoutubeApp.js @@ -1,6 +1,5 @@ import VideoStorage from "./VideoStorage"; import SearchModalView from "./view/SearchModalView"; -import mockObject from "./mockObject"; import getSearchResult from "./api/getSearchResult"; import { DELAY_TIME } from "./constants/constants"; import { throttle, checkKeywordValid, isScrollToBottom } from "./utils/utils"; @@ -88,18 +87,10 @@ export default class YoutubeApp { this.searchModalView.renderSkeleton(); this.keyword = keyword; - /** - * 목 데이터로 검색 결과 대체 - */ - // const responseData = { - // items: mockObject(), - // nextPageToken: "ABCDEF", - // }; const responseData = await getSearchResult(this.keyword); this.nextPageToken = responseData.nextPageToken; - // 검색 결과가 없을 경우 if (responseData.items.length === 0) { this.searchModalView.renderNoResultPage(); return; @@ -111,13 +102,6 @@ export default class YoutubeApp { async searchNextPage() { this.searchModalView.renderSkeleton(); - /** - * 목 데이터로 검색 결과 대체 - */ - // const responseData = { - // items: mockObject(), - // nextPageToken: "ABCDEF", - // }; const responseData = await getSearchResult( this.keyword, this.nextPageToken diff --git a/src/js/api/getSearchResult.js b/src/js/api/getSearchResult.js index 941b56915..e9c985292 100644 --- a/src/js/api/getSearchResult.js +++ b/src/js/api/getSearchResult.js @@ -1,12 +1,18 @@ -import { ITEMS_PER_REQUEST } from "../constants/constants"; +import { ITEMS_PER_REQUEST, DEVELOP_MODE } from "../constants/constants"; +import mockObject from "../mockObject"; export default async function getSearchResult( searchKeyword, nextPageToken = "" ) { - const usageRedirect = "https://unruffled-turing-aacdf7.netlify.app"; - const kkojaeRedirect = "https://clever-aryabhata-ff1fc1.netlify.app"; - const REDIRECT_SERVER_HOST = usageRedirect; + if (DEVELOP_MODE) { + return { + items: mockObject(), + nextPageToken: "ABCDEF", + }; + } + + const REDIRECT_SERVER_HOST = "https://unruffled-turing-aacdf7.netlify.app"; const url = new URL("youtube/v3/search", REDIRECT_SERVER_HOST); const parameters = new URLSearchParams({ @@ -31,6 +37,7 @@ export default async function getSearchResult( return data; } catch (error) { - console.error(error); + // TODO: 에러 메시지 핸들링 객체 만들어서 붙여주기 + return null; } } diff --git a/src/js/constants/constants.js b/src/js/constants/constants.js index 43f899d66..8e33d5412 100644 --- a/src/js/constants/constants.js +++ b/src/js/constants/constants.js @@ -9,3 +9,5 @@ export const DELAY_TIME = 300; export const ITEMS_PER_REQUEST = 10; export const ALLOCATE_FOR_RENDER_PX = 40; export const STORAGE_MAX_COUNT = 100; + +export const DEVELOP_MODE = true; From d174a690f2f2a81e7c09885c676ad8ad730fdf8e Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Wed, 16 Mar 2022 21:52:16 +0900 Subject: [PATCH 04/37] =?UTF-8?q?test:=20=EC=B2=98=EC=9D=8C=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=ED=95=98=EB=A9=B4=20=EC=A0=80=EC=9E=A5=EB=90=9C=20?= =?UTF-8?q?=EC=98=81=EC=83=81=EC=9D=B4=20=EC=97=86=EB=8B=A4=EB=8A=94=20?= =?UTF-8?q?=EC=95=88=EB=82=B4=20=EB=AC=B8=EA=B5=AC=EA=B0=80=20=EB=82=98?= =?UTF-8?q?=ED=83=80=EB=82=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cypress/integration/app.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cypress/integration/app.test.js b/cypress/integration/app.test.js index 840bb2186..fd887a6ed 100644 --- a/cypress/integration/app.test.js +++ b/cypress/integration/app.test.js @@ -1,6 +1,6 @@ import { ITEMS_PER_REQUEST } from "../../src/js/constants/constants"; -describe("보고싶은 영상 찾기 모달창 전체 로직 테스트", () => { +describe("나만의 유튜브 강의실 전체 플로우 테스트", () => { before(() => { cy.visit("./index.html"); }); @@ -8,6 +8,10 @@ describe("보고싶은 영상 찾기 모달창 전체 로직 테스트", () => { const searchKeyword = "xooos"; const errorSearchKeyword = `\!\@\!\@\$\!\%\@\$\^\%\&\$\^\*\%\!\@\!\$\!\%\&\(\^\*\%\$\!\@!@$$!#@!#)_)&_%^_)&%_^)&_@!@#!#$@#$%$@#^%&$%^&#$@$^#%&$%^$^%*$^&^@#$@#$@#%@#$^#%&^**#^#$%@#$@#$^@#$!$@#%@#$%#$^#$%^$%@#$!@#!@#%)^_&)%_^$%#$%#$^#%^#%^#^&_%^_)&_#$)%_)#_$)%#_$%!@#!@$#$!#@!#)_)&_%^_)&%_^)&_%)^_&)%_^&_%^_)&_#$)%_)#_$)%#_$\%`; + it("처음 실행하면 저장된 영상이 없다는 안내 문구가 나타난다.", () => { + cy.get(".no-result__description").should("be.visible"); + }); + it("초기 화면에서 검색 버튼을 누르면 보고싶은 영상 찾기 모달창이 나타난다.", () => { cy.openSearchModal(); cy.get(".modal-container").should("be.visible"); From ee81b913f1ed683a3cdd0ce6cf7ccf5442b774b5 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Wed, 16 Mar 2022 23:02:02 +0900 Subject: [PATCH 05/37] =?UTF-8?q?feat:=20=EC=B2=98=EC=9D=8C=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=ED=95=98=EB=A9=B4=20=EC=A0=80=EC=9E=A5=EB=90=9C=20?= =?UTF-8?q?=EC=98=81=EC=83=81=EC=9D=B4=20=EC=97=86=EB=8B=A4=EB=8A=94=20?= =?UTF-8?q?=EC=95=88=EB=82=B4=20=EB=AC=B8=EA=B5=AC=EA=B0=80=20=EB=82=98?= =?UTF-8?q?=ED=83=80=EB=82=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - localStorage를 확인하여 비어있는 경우에만 안내 문구 표시 --- index.html | 18 ++++++++++++++++- src/css/app.css | 36 +++++++++++++++++++++++++++------ src/css/index.css | 8 +++++++- src/css/modal.css | 8 -------- src/js/YoutubeApp.js | 13 +++++++++++- src/js/templates.js | 25 +++++++++++++++++++---- src/js/view/SearchModalView.js | 6 +++--- src/js/view/VideoStorageView.js | 21 +++++++++++++++++++ 8 files changed, 111 insertions(+), 24 deletions(-) create mode 100644 src/js/view/VideoStorageView.js diff --git a/index.html b/index.html index ced1e263f..9d100a603 100644 --- a/index.html +++ b/index.html @@ -9,12 +9,28 @@

👩🏻‍💻 나만의 유튜브 강의실 👨🏻‍💻

+
+ +
    +
    - + `; }, + savedVideoItems(videoStorage) { + return videoStorage + .map((item) => + this.savedVideoItem({ + id: item.videoId, + channel: item.channel, + thumbnail: item.thumbnail, + title: item.title, + date: item.publishTime, + isWatched: item.isWatched, + }) + ) + .join(""); + }, }; export default generateTemplate; diff --git a/src/js/utils/dom.js b/src/js/utils/dom.js index 943c51eeb..e4b65afa4 100644 --- a/src/js/utils/dom.js +++ b/src/js/utils/dom.js @@ -4,8 +4,19 @@ export const bindEventListener = (element, type, callback) => { element.addEventListener(type, callback); }; -export const findTargetDataset = (target, parentSelector) => { - return target.closest(parentSelector).dataset; +export const getTargetVideoData = (target, parentSelector) => { + const parentElement = target.closest(parentSelector); + const videoDataObject = { + videoId: parentElement.dataset.videoId, + channel: parentElement.querySelector(".video-item__channel-name").innerText, + thumbnail: parentElement.querySelector(".video-item__thumbnail").src, + title: parentElement.querySelector(".video-item__title").innerText, + publishTime: parentElement.querySelector(".video-item__published-date") + .innerText, + iswatched: false, + }; + + return videoDataObject; }; export const scrollToTop = (element = document.querySelector("body")) => { diff --git a/src/js/view/SearchModalView.js b/src/js/view/SearchModalView.js index e7b4c1f22..27a29bf64 100644 --- a/src/js/view/SearchModalView.js +++ b/src/js/view/SearchModalView.js @@ -47,11 +47,11 @@ export default class SearchModalView { ); } - renderSearchResult(responseData, videoStorage) { + renderSearchResult(responseData, videoIdArray) { this.unrenderSkeleton(); const videoItemTemplate = generateTemplate.videoItems( responseData.items, - videoStorage + videoIdArray ); this.videoList.insertAdjacentHTML("beforeend", videoItemTemplate); diff --git a/src/js/view/VideoStorageView.js b/src/js/view/VideoStorageView.js index 40926c3fc..675dd1b4a 100644 --- a/src/js/view/VideoStorageView.js +++ b/src/js/view/VideoStorageView.js @@ -11,11 +11,24 @@ export default class VideoStorageView { const message = "저장된 영상이 없습니다
    나만의 영상을 검색하여 저장해보세요"; - this.savedVideoSection.removeChild(this.savedVideoList); - this.savedVideoSection.classList.add("search-result--no-result"); + this.savedVideoList.classList.add("hide"); this.savedVideoSection.insertAdjacentHTML( "beforeend", generateTemplate.noResult(notFountImage, message) ); }; + + renderSavedVideo = (videoData) => { + const videoItemTemplate = generateTemplate.savedVideoItems(videoData); + const noResultDiv = document.querySelector(".no-result"); + + this.savedVideoList.classList.remove("hide"); + this.savedVideoList.insertAdjacentHTML("beforeend", videoItemTemplate); + + if (!noResultDiv) { + return; + } + + noResultDiv.classList.add("hide"); + }; } From ecf3d135d280b6491b18086812297af05a6e5ada Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Fri, 18 Mar 2022 17:58:04 +0900 Subject: [PATCH 08/37] =?UTF-8?q?test:=20=EC=B4=88=EA=B8=B0=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=EC=84=9C=20=EC=A0=80=EC=9E=A5=EB=90=9C=20?= =?UTF-8?q?=EC=98=81=EC=83=81=EC=9D=98=20=E2=9C=85=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=EC=9D=84=20=EB=88=8C=EB=9F=AC=20=EB=B3=B8=20=EC=98=81=EC=83=81?= =?UTF-8?q?=20=EC=84=B9=EC=85=98=EC=9C=BC=EB=A1=9C=20=EC=98=AE=EA=B8=B8=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EC=96=B4=EC=95=BC=20=ED=95=9C=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cypress/integration/app.test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cypress/integration/app.test.js b/cypress/integration/app.test.js index 7ea6ed4f4..226e0bd12 100644 --- a/cypress/integration/app.test.js +++ b/cypress/integration/app.test.js @@ -69,4 +69,9 @@ describe("나만의 유튜브 강의실 전체 플로우 테스트", () => { it("초기 화면에서 저장된 영상을 확인할 수 있어야 한다.", () => { cy.get(".video-item").should("be.visible"); }); + + it("초기 화면에서 저장된 영상의 ✅ 버튼을 눌러 본 영상 섹션으로 옮길 수 있어야 한다.", () => { + cy.get(".video-item__watched-button").eq(0).click(); + cy.get(".video-item").should("be.not.visible"); + }); }); From 09e9a8ebf61702553676d91b4e173cee0847375e Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Fri, 18 Mar 2022 21:49:54 +0900 Subject: [PATCH 09/37] =?UTF-8?q?feat:=20=EC=B4=88=EA=B8=B0=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=EC=84=9C=20=EC=A0=80=EC=9E=A5=EB=90=9C=20?= =?UTF-8?q?=EC=98=81=EC=83=81=EC=9D=98=20=E2=9C=85=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=EC=9D=84=20=EB=88=8C=EB=9F=AC=20=EB=B3=B8=20=EC=98=81=EC=83=81?= =?UTF-8?q?=20=EC=84=B9=EC=85=98=EC=9C=BC=EB=A1=9C=20=EC=98=AE=EA=B8=B0?= =?UTF-8?q?=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/VideoStorage.js | 9 ++++++++ src/js/YoutubeApp.js | 24 +++++++++++++++---- src/js/templates.js | 41 ++++++++++++++++++++++++--------- src/js/utils/dom.js | 2 +- src/js/view/VideoStorageView.js | 11 +++++++-- 5 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/js/VideoStorage.js b/src/js/VideoStorage.js index 2165301c8..39a13841b 100644 --- a/src/js/VideoStorage.js +++ b/src/js/VideoStorage.js @@ -25,4 +25,13 @@ export default class VideoStorage { getVideoIdArray() { return this.getStorage().map((item) => item.videoId); } + + setVideoStateWatched(target, state) { + const targetIndex = this.getStorage().findIndex( + (item) => item.videoId === target + ); + + this.videos[targetIndex].isWatched = state; + localStorage.setItem("videos", JSON.stringify(this.videos)); + } } diff --git a/src/js/YoutubeApp.js b/src/js/YoutubeApp.js index c38cea4a8..999928fb7 100644 --- a/src/js/YoutubeApp.js +++ b/src/js/YoutubeApp.js @@ -9,12 +9,15 @@ import { bindEventListener, getTargetVideoData } from "./utils/dom"; export default class YoutubeApp { constructor() { this.videoList = document.querySelector(".video-list"); + this.savedVideoList = document.querySelector(".saved-video-list"); - this.#bindEvents(); this.videoStorage = new VideoStorage(); - this.VideoStorageView = new VideoStorageView(); + this.videoStorageView = new VideoStorageView(); this.searchModalView = new SearchModalView(); + this.isWatchedVideoOnly = false; + + this.#bindEvents(); this.#reloadStorageData(); } @@ -34,6 +37,7 @@ export default class YoutubeApp { "scroll", throttle(this.#onScrollVideoList, DELAY_TIME) ); + bindEventListener(this.savedVideoList, "click", this.#onClickWatchedButton); bindEventListener(this.videoList, "click", this.#onClickSaveButton); bindEventListener( document.querySelector(".dimmer"), @@ -45,11 +49,14 @@ export default class YoutubeApp { #reloadStorageData = () => { console.log(this.videoStorage.getStorage()); if (!this.videoStorage.getStorage().length) { - this.VideoStorageView.renderEmptyStorage(); + this.videoStorageView.renderEmptyStorage(); return; } - this.VideoStorageView.renderSavedVideo(this.videoStorage.getStorage()); + this.videoStorageView.renderSavedVideo( + this.videoStorage.getStorage(), + this.isWatchedVideoOnly + ); }; #onClickSearchModalButton = () => { @@ -61,6 +68,15 @@ export default class YoutubeApp { this.#reloadStorageData(); }; + #onClickWatchedButton = ({ target }) => { + if (!target.matches(".video-item__watched-button")) return; + + const videoData = getTargetVideoData(target, ".video-item"); + this.videoStorage.setVideoStateWatched(videoData.videoId, true); + + this.videoStorageView.hideElement(target.closest(".video-item")); + }; + #onClickSaveButton = ({ target }) => { if (!target.matches(".video-item__save-button")) return; diff --git a/src/js/templates.js b/src/js/templates.js index e57da55e9..70535bb4c 100644 --- a/src/js/templates.js +++ b/src/js/templates.js @@ -78,18 +78,37 @@ const generateTemplate = { `; }, - savedVideoItems(videoStorage) { - return videoStorage - .map((item) => - this.savedVideoItem({ - id: item.videoId, - channel: item.channel, - thumbnail: item.thumbnail, - title: item.title, - date: item.publishTime, - isWatched: item.isWatched, + savedVideoItems(videoStorage, watchedVideoOnly) { + if (watchedVideoOnly) { + return videoStorage + .map((item) => { + return item.isWatched + ? this.savedVideoItem({ + id: item.videoId, + channel: item.channel, + thumbnail: item.thumbnail, + title: item.title, + date: item.publishTime, + isWatched: item.isWatched, + }) + : ""; }) - ) + .join(""); + } + + return videoStorage + .map((item) => { + return item.isWatched + ? "" + : this.savedVideoItem({ + id: item.videoId, + channel: item.channel, + thumbnail: item.thumbnail, + title: item.title, + date: item.publishTime, + isWatched: item.isWatched, + }); + }) .join(""); }, }; diff --git a/src/js/utils/dom.js b/src/js/utils/dom.js index e4b65afa4..547785d64 100644 --- a/src/js/utils/dom.js +++ b/src/js/utils/dom.js @@ -13,7 +13,7 @@ export const getTargetVideoData = (target, parentSelector) => { title: parentElement.querySelector(".video-item__title").innerText, publishTime: parentElement.querySelector(".video-item__published-date") .innerText, - iswatched: false, + isWatched: false, }; return videoDataObject; diff --git a/src/js/view/VideoStorageView.js b/src/js/view/VideoStorageView.js index 675dd1b4a..bb8baeb3f 100644 --- a/src/js/view/VideoStorageView.js +++ b/src/js/view/VideoStorageView.js @@ -18,8 +18,11 @@ export default class VideoStorageView { ); }; - renderSavedVideo = (videoData) => { - const videoItemTemplate = generateTemplate.savedVideoItems(videoData); + renderSavedVideo = (videoData, watchedVideoOnly) => { + const videoItemTemplate = generateTemplate.savedVideoItems( + videoData, + watchedVideoOnly + ); const noResultDiv = document.querySelector(".no-result"); this.savedVideoList.classList.remove("hide"); @@ -31,4 +34,8 @@ export default class VideoStorageView { noResultDiv.classList.add("hide"); }; + + hideElement = (target) => { + target.classList.add("hide"); + }; } From f88f82072d77e9bcd928c51d381a51f1f4195dae Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Fri, 18 Mar 2022 21:52:46 +0900 Subject: [PATCH 10/37] =?UTF-8?q?test:=20=EC=B4=88=EA=B8=B0=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=EC=84=9C=20=EB=B3=B8=20=EC=98=81=EC=83=81=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=EC=9D=84=20=EB=88=8C=EB=9F=AC=20=EB=B3=B8=20?= =?UTF-8?q?=EC=98=81=EC=83=81=20=EB=AA=A9=EB=A1=9D=EC=9D=84=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=ED=95=A0=20=EC=88=98=20=EC=9E=88=EC=96=B4=EC=95=BC=20?= =?UTF-8?q?=ED=95=9C=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cypress/integration/app.test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cypress/integration/app.test.js b/cypress/integration/app.test.js index 226e0bd12..99da9aab4 100644 --- a/cypress/integration/app.test.js +++ b/cypress/integration/app.test.js @@ -74,4 +74,9 @@ describe("나만의 유튜브 강의실 전체 플로우 테스트", () => { cy.get(".video-item__watched-button").eq(0).click(); cy.get(".video-item").should("be.not.visible"); }); + + it("초기 화면에서 본 영상 버튼을 눌러 본 영상 목록을 확인할 수 있어야 한다.", () => { + cy.get("#watched-video-button").click(); + cy.get(".video-item").should("be.visible"); + }); }); From 05f7d8e8bca40d6b230f4789e2ad0f99bf7690a8 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Fri, 18 Mar 2022 22:13:13 +0900 Subject: [PATCH 11/37] =?UTF-8?q?feat:=20=EC=B4=88=EA=B8=B0=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=EC=84=9C=20=EB=B3=B8=20=EC=98=81=EC=83=81=20?= =?UTF-8?q?/=20=EB=B3=BC=20=EC=98=81=EC=83=81=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EA=B0=81=EA=B0=81=EC=9D=98=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=EC=9D=84=20=EB=B6=88=EB=9F=AC=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/YoutubeApp.js | 20 ++++++++++++++++++++ src/js/view/VideoStorageView.js | 21 ++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/js/YoutubeApp.js b/src/js/YoutubeApp.js index 999928fb7..23493add5 100644 --- a/src/js/YoutubeApp.js +++ b/src/js/YoutubeApp.js @@ -22,6 +22,16 @@ export default class YoutubeApp { } #bindEvents() { + bindEventListener( + document.querySelector("#watch-later-video-button"), + "click", + this.#onClickWatchLaterVideoListButton + ); + bindEventListener( + document.querySelector("#watched-video-button"), + "click", + this.#onClickWatchedVideoListButton + ); bindEventListener( document.querySelector("#search-modal-button"), "click", @@ -59,6 +69,16 @@ export default class YoutubeApp { ); }; + #onClickWatchLaterVideoListButton = () => { + this.isWatchedVideoOnly = false; + this.#reloadStorageData(); + }; + + #onClickWatchedVideoListButton = () => { + this.isWatchedVideoOnly = true; + this.#reloadStorageData(); + }; + #onClickSearchModalButton = () => { this.searchModalView.openSearchModal(); }; diff --git a/src/js/view/VideoStorageView.js b/src/js/view/VideoStorageView.js index bb8baeb3f..99db435b3 100644 --- a/src/js/view/VideoStorageView.js +++ b/src/js/view/VideoStorageView.js @@ -25,8 +25,10 @@ export default class VideoStorageView { ); const noResultDiv = document.querySelector(".no-result"); + this.#renderNavButtonStateChanged(watchedVideoOnly); + this.savedVideoList.classList.remove("hide"); - this.savedVideoList.insertAdjacentHTML("beforeend", videoItemTemplate); + this.savedVideoList.innerHTML = videoItemTemplate; if (!noResultDiv) { return; @@ -35,6 +37,23 @@ export default class VideoStorageView { noResultDiv.classList.add("hide"); }; + #renderNavButtonStateChanged = (watchedVideoOnly) => { + if (watchedVideoOnly) { + document + .querySelector("#watch-later-video-button") + .classList.remove("selected"); + document.querySelector("#watched-video-button").classList.add("selected"); + return; + } + + document + .querySelector("#watch-later-video-button") + .classList.add("selected"); + document + .querySelector("#watched-video-button") + .classList.remove("selected"); + }; + hideElement = (target) => { target.classList.add("hide"); }; From 60d3542e76e6c0cbe7dfa5af38d6fccf3d8d1cf6 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Fri, 18 Mar 2022 22:14:11 +0900 Subject: [PATCH 12/37] =?UTF-8?q?chore:=20es-lint=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - class-methods-use-this: 0 추가 --- .eslintrc.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.json b/.eslintrc.json index 42ad3ce90..42ecd5470 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -21,6 +21,7 @@ "no-undefined": 0, "no-constant-condition": 0, "no-unused-private-class-members": 0, + "class-methods-use-this": 0, "lines-between-class-members": 0, "no-useless-escape": 0, "import/no-unresolved": 0, From f1a1189b7a44890221d79d7040d835b225345476 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Fri, 18 Mar 2022 22:18:44 +0900 Subject: [PATCH 13/37] =?UTF-8?q?test:=20=EB=B3=B8=20=EC=98=81=EC=83=81=20?= =?UTF-8?q?=EC=84=B9=EC=85=98=EC=97=90=EC=84=9C=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=EB=90=9C=20=EC=98=81=EC=83=81=EC=9D=98=20=E2=9C=85=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=EC=9D=84=20=EB=88=8C=EB=9F=AC=20=EB=B3=BC=20=EC=98=81?= =?UTF-8?q?=EC=83=81=20=EC=84=B9=EC=85=98=EC=9C=BC=EB=A1=9C=20=EC=98=AE?= =?UTF-8?q?=EA=B8=B8=20=EC=88=98=20=EC=9E=88=EC=96=B4=EC=95=BC=20=ED=95=9C?= =?UTF-8?q?=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 짝 테스트 설명도 함께 변경 : "초기 화면에서" -> "볼 영상 섹션에서" --- cypress/integration/app.test.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cypress/integration/app.test.js b/cypress/integration/app.test.js index 99da9aab4..0120d89de 100644 --- a/cypress/integration/app.test.js +++ b/cypress/integration/app.test.js @@ -70,7 +70,7 @@ describe("나만의 유튜브 강의실 전체 플로우 테스트", () => { cy.get(".video-item").should("be.visible"); }); - it("초기 화면에서 저장된 영상의 ✅ 버튼을 눌러 본 영상 섹션으로 옮길 수 있어야 한다.", () => { + it("볼 영상 섹션에서 저장된 영상의 ✅ 버튼을 눌러 본 영상 섹션으로 옮길 수 있어야 한다.", () => { cy.get(".video-item__watched-button").eq(0).click(); cy.get(".video-item").should("be.not.visible"); }); @@ -79,4 +79,10 @@ describe("나만의 유튜브 강의실 전체 플로우 테스트", () => { cy.get("#watched-video-button").click(); cy.get(".video-item").should("be.visible"); }); + + it("본 영상 섹션에서 저장된 영상의 ✅ 버튼을 눌러 볼 영상 섹션으로 옮길 수 있어야 한다.", () => { + cy.get(".video-item__watched-button").eq(0).click(); + cy.get("#watch-later-video-button").click(); + cy.get(".video-item").should("be.visible"); + }); }); From 104123f734beb5dce0e31f1f84eeabacebb99b02 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Fri, 18 Mar 2022 23:31:21 +0900 Subject: [PATCH 14/37] =?UTF-8?q?feat:=20=E2=9C=85=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=98=81=EC=83=81=EC=9D=98=20=EC=8B=9C?= =?UTF-8?q?=EC=B2=AD=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/VideoStorage.js | 4 ++-- src/js/YoutubeApp.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/js/VideoStorage.js b/src/js/VideoStorage.js index 39a13841b..5879aef27 100644 --- a/src/js/VideoStorage.js +++ b/src/js/VideoStorage.js @@ -26,12 +26,12 @@ export default class VideoStorage { return this.getStorage().map((item) => item.videoId); } - setVideoStateWatched(target, state) { + setVideoStateWatched(target) { const targetIndex = this.getStorage().findIndex( (item) => item.videoId === target ); - this.videos[targetIndex].isWatched = state; + this.videos[targetIndex].isWatched = !this.videos[targetIndex].isWatched; localStorage.setItem("videos", JSON.stringify(this.videos)); } } diff --git a/src/js/YoutubeApp.js b/src/js/YoutubeApp.js index 23493add5..024d81ad1 100644 --- a/src/js/YoutubeApp.js +++ b/src/js/YoutubeApp.js @@ -92,7 +92,7 @@ export default class YoutubeApp { if (!target.matches(".video-item__watched-button")) return; const videoData = getTargetVideoData(target, ".video-item"); - this.videoStorage.setVideoStateWatched(videoData.videoId, true); + this.videoStorage.setVideoStateWatched(videoData.videoId); this.videoStorageView.hideElement(target.closest(".video-item")); }; From eb5ac9545d87c903f070d96d4a7bc89c97883fe5 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sat, 19 Mar 2022 15:52:03 +0900 Subject: [PATCH 15/37] =?UTF-8?q?test:=20=EC=B4=88=EA=B8=B0=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=EC=84=9C=20=EC=A0=80=EC=9E=A5=EB=90=9C=20?= =?UTF-8?q?=EC=98=81=EC=83=81=EC=9D=98=20=F0=9F=97=91=EF=B8=8F=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=EC=9D=84=20=EB=88=8C=EB=9F=AC=20=EC=98=81=EC=83=81?= =?UTF-8?q?=EC=9D=84=20=EC=82=AD=EC=A0=9C=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EC=96=B4=EC=95=BC=20=ED=95=9C=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cypress/integration/app.test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cypress/integration/app.test.js b/cypress/integration/app.test.js index 0120d89de..fa54348e9 100644 --- a/cypress/integration/app.test.js +++ b/cypress/integration/app.test.js @@ -85,4 +85,9 @@ describe("나만의 유튜브 강의실 전체 플로우 테스트", () => { cy.get("#watch-later-video-button").click(); cy.get(".video-item").should("be.visible"); }); + + it("초기 화면에서 저장된 영상의 🗑️ 버튼을 눌러 영상을 삭제할 수 있어야 한다.", () => { + cy.get(".video-item__delete-button").eq(0).click(); + cy.get(".video-item").should("be.not.visible"); + }); }); From 370faa6d4912b1fc37cb09d6d9328d53c3dc5073 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sat, 19 Mar 2022 16:16:40 +0900 Subject: [PATCH 16/37] =?UTF-8?q?feat:=20=EC=B4=88=EA=B8=B0=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=EC=84=9C=20=EC=A0=80=EC=9E=A5=EB=90=9C=20?= =?UTF-8?q?=EC=98=81=EC=83=81=EC=9D=98=20=F0=9F=97=91=EF=B8=8F=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=EC=9D=84=20=EB=88=8C=EB=9F=AC=20=EC=98=81=EC=83=81?= =?UTF-8?q?=EC=9D=84=20=EC=82=AD=EC=A0=9C=ED=95=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/VideoStorage.js | 9 +++++++++ src/js/YoutubeApp.js | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/js/VideoStorage.js b/src/js/VideoStorage.js index 5879aef27..8ca1f59c7 100644 --- a/src/js/VideoStorage.js +++ b/src/js/VideoStorage.js @@ -34,4 +34,13 @@ export default class VideoStorage { this.videos[targetIndex].isWatched = !this.videos[targetIndex].isWatched; localStorage.setItem("videos", JSON.stringify(this.videos)); } + + deleteVideo(target) { + const targetIndex = this.getStorage().findIndex( + (item) => item.videoId === target + ); + + this.videos.splice(targetIndex, 1); + localStorage.setItem("videos", JSON.stringify(this.videos)); + } } diff --git a/src/js/YoutubeApp.js b/src/js/YoutubeApp.js index 024d81ad1..aa8242592 100644 --- a/src/js/YoutubeApp.js +++ b/src/js/YoutubeApp.js @@ -48,6 +48,7 @@ export default class YoutubeApp { throttle(this.#onScrollVideoList, DELAY_TIME) ); bindEventListener(this.savedVideoList, "click", this.#onClickWatchedButton); + bindEventListener(this.savedVideoList, "click", this.#onClickDeleteButton); bindEventListener(this.videoList, "click", this.#onClickSaveButton); bindEventListener( document.querySelector(".dimmer"), @@ -97,6 +98,15 @@ export default class YoutubeApp { this.videoStorageView.hideElement(target.closest(".video-item")); }; + #onClickDeleteButton = ({ target }) => { + if (!target.matches(".video-item__delete-button")) return; + + const videoData = getTargetVideoData(target, ".video-item"); + this.videoStorage.deleteVideo(videoData.videoId); + + this.videoStorageView.hideElement(target.closest(".video-item")); + }; + #onClickSaveButton = ({ target }) => { if (!target.matches(".video-item__save-button")) return; From 99a27130ae0692a8483b90c8bec17d4f11a26011 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sat, 19 Mar 2022 17:05:33 +0900 Subject: [PATCH 17/37] =?UTF-8?q?fix:=20no=20result=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EA=B0=80=20=EC=A4=91=EB=B3=B5=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=EB=90=98=EB=8D=98=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 6 ++++++ src/js/YoutubeApp.js | 12 ++++++++++++ src/js/view/VideoStorageView.js | 19 ++++++------------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/index.html b/index.html index 9d100a603..45a6d1874 100644 --- a/index.html +++ b/index.html @@ -28,6 +28,12 @@

    👩🏻‍💻 나만의 유튜브 강
      +
      + no result image +

      + 저장된 영상이 없습니다
      나만의 영상을 검색하여 저장해보세요 +

      +
      diff --git a/src/js/YoutubeApp.js b/src/js/YoutubeApp.js index aa8242592..cd2d13290 100644 --- a/src/js/YoutubeApp.js +++ b/src/js/YoutubeApp.js @@ -71,12 +71,24 @@ export default class YoutubeApp { }; #onClickWatchLaterVideoListButton = () => { + if (!this.isWatchedVideoOnly) { + return; + } + this.isWatchedVideoOnly = false; + this.videoStorageView.renderNavButtonStateChanged(this.isWatchedVideoOnly); + this.#reloadStorageData(); }; #onClickWatchedVideoListButton = () => { + if (this.isWatchedVideoOnly) { + return; + } + this.isWatchedVideoOnly = true; + this.videoStorageView.renderNavButtonStateChanged(this.isWatchedVideoOnly); + this.#reloadStorageData(); }; diff --git a/src/js/view/VideoStorageView.js b/src/js/view/VideoStorageView.js index 99db435b3..ea94bd3dd 100644 --- a/src/js/view/VideoStorageView.js +++ b/src/js/view/VideoStorageView.js @@ -5,17 +5,13 @@ export default class VideoStorageView { constructor() { this.savedVideoSection = document.querySelector(".saved-video__section"); this.savedVideoList = document.querySelector(".saved-video-list"); + this.noResultDiv = document.querySelector(".no-result"); } renderEmptyStorage = () => { - const message = - "저장된 영상이 없습니다
      나만의 영상을 검색하여 저장해보세요"; - this.savedVideoList.classList.add("hide"); - this.savedVideoSection.insertAdjacentHTML( - "beforeend", - generateTemplate.noResult(notFountImage, message) - ); + this.noResultDiv.classList.remove("hide"); + document.querySelector(".no-result__image").src = notFountImage; }; renderSavedVideo = (videoData, watchedVideoOnly) => { @@ -23,21 +19,18 @@ export default class VideoStorageView { videoData, watchedVideoOnly ); - const noResultDiv = document.querySelector(".no-result"); - - this.#renderNavButtonStateChanged(watchedVideoOnly); this.savedVideoList.classList.remove("hide"); this.savedVideoList.innerHTML = videoItemTemplate; - if (!noResultDiv) { + if (!this.noResultDiv) { return; } - noResultDiv.classList.add("hide"); + this.noResultDiv.classList.add("hide"); }; - #renderNavButtonStateChanged = (watchedVideoOnly) => { + renderNavButtonStateChanged = (watchedVideoOnly) => { if (watchedVideoOnly) { document .querySelector("#watch-later-video-button") From 5df0f9a196b587ec654ef5f5323bdc89f213c6b7 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sat, 19 Mar 2022 18:19:35 +0900 Subject: [PATCH 18/37] =?UTF-8?q?fix:=20=EB=B9=84=EB=94=94=EC=98=A4=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EC=97=86=EC=9D=8C=20=ED=99=94=EB=A9=B4=EC=9D=B4=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8D=98=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/VideoStorage.js | 9 +++++++++ src/js/YoutubeApp.js | 6 +++--- src/js/view/VideoStorageView.js | 4 ---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/js/VideoStorage.js b/src/js/VideoStorage.js index 8ca1f59c7..be1e0b8d9 100644 --- a/src/js/VideoStorage.js +++ b/src/js/VideoStorage.js @@ -9,6 +9,15 @@ export default class VideoStorage { return this.videos.includes(responseId); } + checkTypeVideoEmpty(isWatchedVideoOnly) { + console.log( + !this.getStorage().some((item) => item.isWatched === isWatchedVideoOnly) + ); + return !this.getStorage().some( + (item) => item.isWatched === isWatchedVideoOnly + ); + } + addVideoData(data) { if (this.videos.length >= STORAGE_MAX_COUNT) { throw new Error(ERROR_MESSAGE.VIDEO_STORAGE_OVERFLOW); diff --git a/src/js/YoutubeApp.js b/src/js/YoutubeApp.js index cd2d13290..5647270ba 100644 --- a/src/js/YoutubeApp.js +++ b/src/js/YoutubeApp.js @@ -59,7 +59,7 @@ export default class YoutubeApp { #reloadStorageData = () => { console.log(this.videoStorage.getStorage()); - if (!this.videoStorage.getStorage().length) { + if (this.videoStorage.checkTypeVideoEmpty(this.isWatchedVideoOnly)) { this.videoStorageView.renderEmptyStorage(); return; } @@ -107,7 +107,7 @@ export default class YoutubeApp { const videoData = getTargetVideoData(target, ".video-item"); this.videoStorage.setVideoStateWatched(videoData.videoId); - this.videoStorageView.hideElement(target.closest(".video-item")); + this.#reloadStorageData(); }; #onClickDeleteButton = ({ target }) => { @@ -116,7 +116,7 @@ export default class YoutubeApp { const videoData = getTargetVideoData(target, ".video-item"); this.videoStorage.deleteVideo(videoData.videoId); - this.videoStorageView.hideElement(target.closest(".video-item")); + this.#reloadStorageData(); }; #onClickSaveButton = ({ target }) => { diff --git a/src/js/view/VideoStorageView.js b/src/js/view/VideoStorageView.js index ea94bd3dd..7219209d3 100644 --- a/src/js/view/VideoStorageView.js +++ b/src/js/view/VideoStorageView.js @@ -46,8 +46,4 @@ export default class VideoStorageView { .querySelector("#watched-video-button") .classList.remove("selected"); }; - - hideElement = (target) => { - target.classList.add("hide"); - }; } From 48105a2e3b8b8debe2995deb48a4dfec8e74cc02 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sat, 19 Mar 2022 19:44:44 +0900 Subject: [PATCH 19/37] =?UTF-8?q?test:=20=EC=B4=88=EA=B8=B0=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=EC=84=9C=20=EC=82=AD=EC=A0=9C=EC=8B=9C=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=EC=97=90=EA=B2=8C=20=EB=AC=BC?= =?UTF-8?q?=EC=96=B4=EB=B3=B4=EB=8A=94=EC=A7=80=20=ED=99=95=EC=9D=B8?= =?UTF-8?q?=ED=95=9C=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cypress/integration/app.test.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/cypress/integration/app.test.js b/cypress/integration/app.test.js index fa54348e9..a22ef6eff 100644 --- a/cypress/integration/app.test.js +++ b/cypress/integration/app.test.js @@ -86,8 +86,27 @@ describe("나만의 유튜브 강의실 전체 플로우 테스트", () => { cy.get(".video-item").should("be.visible"); }); - it("초기 화면에서 저장된 영상의 🗑️ 버튼을 눌러 영상을 삭제할 수 있어야 한다.", () => { + it("초기 화면에서 저장된 영상의 🗑️ 버튼을 눌러 영상을 삭제할지 확인할 수 있어야 한다.", () => { cy.get(".video-item__delete-button").eq(0).click(); + + cy.on("window:confirm", (text) => { + console.log(text); + expect(text).to.be.calledWith("정말로 삭제하시겠습니까?"); + return false; + }); + + cy.get(".video-item").should("be.visible"); + }); + + it("영상을 삭제할지 확인하는 화면에서 확인을 눌러 삭제할 수 있어야 한다.", () => { + cy.get(".video-item__delete-button").eq(0).click(); + + cy.on("window:confirm", (text) => { + console.log(text); + expect(text).to.be.calledWith("정말로 삭제하시겠습니까?"); + return true; + }); + cy.get(".video-item").should("be.not.visible"); }); }); From 82cfd77381b6cd7217f0adf5936534504bb49389 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sat, 19 Mar 2022 19:58:22 +0900 Subject: [PATCH 20/37] =?UTF-8?q?feat:=20=EC=98=81=EC=83=81=EC=9D=84=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=ED=95=98=EA=B8=B0=20=EC=A0=84=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=EC=97=90=EA=B2=8C=20=EB=AC=BC=EC=96=B4?= =?UTF-8?q?=EB=B3=B4=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cypress/integration/app.test.js | 4 ++-- src/js/YoutubeApp.js | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cypress/integration/app.test.js b/cypress/integration/app.test.js index a22ef6eff..4db5038c3 100644 --- a/cypress/integration/app.test.js +++ b/cypress/integration/app.test.js @@ -91,7 +91,7 @@ describe("나만의 유튜브 강의실 전체 플로우 테스트", () => { cy.on("window:confirm", (text) => { console.log(text); - expect(text).to.be.calledWith("정말로 삭제하시겠습니까?"); + expect(text).to.contains("정말로 삭제하시겠습니까?"); return false; }); @@ -103,7 +103,7 @@ describe("나만의 유튜브 강의실 전체 플로우 테스트", () => { cy.on("window:confirm", (text) => { console.log(text); - expect(text).to.be.calledWith("정말로 삭제하시겠습니까?"); + expect(text).to.contains("정말로 삭제하시겠습니까?"); return true; }); diff --git a/src/js/YoutubeApp.js b/src/js/YoutubeApp.js index 5647270ba..ddb35de17 100644 --- a/src/js/YoutubeApp.js +++ b/src/js/YoutubeApp.js @@ -111,7 +111,13 @@ export default class YoutubeApp { }; #onClickDeleteButton = ({ target }) => { - if (!target.matches(".video-item__delete-button")) return; + if (!target.matches(".video-item__delete-button")) { + return; + } + + if (!window.confirm("정말로 삭제하시겠습니까?")) { + return; + } const videoData = getTargetVideoData(target, ".video-item"); this.videoStorage.deleteVideo(videoData.videoId); From 9955e3450b51309338bfdacd64d7a65b8f326ee7 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sat, 19 Mar 2022 19:59:51 +0900 Subject: [PATCH 21/37] =?UTF-8?q?chore:=20es-lint=20no-alert=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.json b/.eslintrc.json index 42ecd5470..248f09d27 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -16,6 +16,7 @@ "no-new": 0, "no-var": "error", "max-depth": ["error", 2], + "no-alert": 0, "no-console": "warn", "no-param-reassign": "error", "no-undefined": 0, From ee21df72a495190b297eea08e0ff173b6b86bca7 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sat, 19 Mar 2022 21:29:40 +0900 Subject: [PATCH 22/37] =?UTF-8?q?feat:=20=EB=B0=98=EC=9D=91=ED=98=95=20?= =?UTF-8?q?=EC=9B=B9=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/css/app.css | 16 ++----------- src/css/index.css | 61 ++++++++++++++++++++++++++++++++++++++++++++++- src/css/modal.css | 3 +-- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/css/app.css b/src/css/app.css index d497bc747..af25a734a 100644 --- a/src/css/app.css +++ b/src/css/app.css @@ -11,7 +11,6 @@ body { .classroom-container__title { text-align: center; font-weight: bold; - font-size: 34px; line-height: 36px; margin-bottom: 64px; } @@ -22,16 +21,6 @@ body { margin-bottom: 40px; } -.button { - cursor: pointer; - border-radius: 4px; - border: none; - font-style: normal; - font-weight: bold; - font-size: 14px; - letter-spacing: 1.25px; -} - .nav__button:hover { background: #ebebeb; } @@ -53,13 +42,12 @@ body { } .saved-video-list { - width: 1040px; - height: 493px; + width: 105%; display: flex; flex-direction: row; flex-wrap: wrap; gap: 32px 20px; - overflow-y: scroll; + overflow-y: hidden; } .video-button__wrapper { diff --git a/src/css/index.css b/src/css/index.css index 3e56ec4c7..598df7704 100644 --- a/src/css/index.css +++ b/src/css/index.css @@ -26,7 +26,66 @@ select { button { height: 36px; - font: inherit; + cursor: pointer; + font-style: normal; + font-weight: bold; padding: 0 10px; + border-radius: 4px; + border: none; background: #f5f5f5; + font-size: 14px; + letter-spacing: 1.25px; +} + +h1 { + font-size: 34px; +} + +h2 { + font-size: 34px; +} + +@media (max-width: 1279px) { + #app { + max-width: 760px; + } + + .search-modal { + max-width: 860px; + } +} + +@media (max-width: 959px) { + #app { + max-width: 500px; + } + + .search-modal { + max-width: 600px; + } +} + +@media (max-width: 599px) { + #app { + max-width: 300px; + } + + .search-modal { + max-width: 360px; + } + + h1, + h2 { + font-size: 22px; + } + + button { + height: 28px; + font-size: 12px; + } + + .search-input__keyword { + width: 200px; + font-size: 10px; + } } diff --git a/src/css/modal.css b/src/css/modal.css index a8ef00057..d9c18c615 100644 --- a/src/css/modal.css +++ b/src/css/modal.css @@ -35,7 +35,6 @@ .search-modal__title { font-weight: bold; - font-size: 34px; line-height: 36px; text-align: center; margin-bottom: 40px; @@ -82,7 +81,7 @@ } .video-list { - width: 1040px; + width: 102%; height: 493px; display: flex; flex-direction: row; From 6bf3540121cab5a3c3f2a2024e87826993407a8d Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sat, 19 Mar 2022 21:44:44 +0900 Subject: [PATCH 23/37] =?UTF-8?q?chore:=20=EC=A3=BC=EC=84=9D,=20=EC=BD=98?= =?UTF-8?q?=EC=86=94=EB=A1=9C=EA=B7=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++++----- cypress/integration/app.test.js | 23 +++++------------------ src/js/VideoStorage.js | 3 --- src/js/YoutubeApp.js | 1 - 4 files changed, 10 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 6fde341f0..2663d50db 100644 --- a/README.md +++ b/README.md @@ -39,13 +39,13 @@ ### Step 2 -- [ ] 가장 처음에는 저장된 영상이 없으므로, 비어있다는 것을 사용자에게 알려주는 상태를 보여준다. -- [ ] 이후 페이지를 방문했을 때 기본 메인 화면은 내가 볼 영상들의 리스트를 보여준다. -- [ ] 영상 카드의 이모지 버튼을 클릭하여 아래와 같은 상태 변경이 가능해야 한다. +- [x] 가장 처음에는 저장된 영상이 없으므로, 비어있다는 것을 사용자에게 알려주는 상태를 보여준다. +- [x] 이후 페이지를 방문했을 때 기본 메인 화면은 내가 볼 영상들의 리스트를 보여준다. +- [x] 영상 카드의 이모지 버튼을 클릭하여 아래와 같은 상태 변경이 가능해야 한다. - ✅ 본 영상으로 체크 - 🗑️ 버튼으로 저장된 리스트에서 삭제할 수 있다. (삭제 시 사용자에게 정말 삭제할 것인지 물어봅니다.) -- [ ] 본 영상, 볼 영상 버튼을 눌러 필터링 할 수 있다. -- [ ] 반응형 웹: 유저가 사용하는 디바이스의 가로 길이에 따라 검색결과의 row 당 column 갯수를 변경한다. +- [x] 본 영상, 볼 영상 버튼을 눌러 필터링 할 수 있다. +- [x] 반응형 웹: 유저가 사용하는 디바이스의 가로 길이에 따라 검색결과의 row 당 column 갯수를 변경한다. - 1280px 이상: 4개 - 960px이상~1280px 미만: 3개 - 600px이상~960px 미만: 2개 diff --git a/cypress/integration/app.test.js b/cypress/integration/app.test.js index 4db5038c3..dd3adeb4a 100644 --- a/cypress/integration/app.test.js +++ b/cypress/integration/app.test.js @@ -6,7 +6,6 @@ describe("나만의 유튜브 강의실 전체 플로우 테스트", () => { }); const searchKeyword = "xooos"; - const errorSearchKeyword = `\!\@\!\@\$\!\%\@\$\^\%\&\$\^\*\%\!\@\!\$\!\%\&\(\^\*\%\$\!\@!@$$!#@!#)_)&_%^_)&%_^)&_@!@#!#$@#$%$@#^%&$%^&#$@$^#%&$%^$^%*$^&^@#$@#$@#%@#$^#%&^**#^#$%@#$@#$^@#$!$@#%@#$%#$^#$%^$%@#$!@#!@#%)^_&)%_^$%#$%#$^#%^#%^#^&_%^_)&_#$)%_)#_$)%#_$%!@#!@$#$!#@!#)_)&_%^_)&%_^)&_%)^_&)%_^&_%^_)&_#$)%_)#_$)%#_$\%`; it("처음 실행하면 저장된 영상이 없다는 안내 문구가 나타난다.", () => { cy.get(".no-result__description").should("be.visible"); @@ -17,23 +16,11 @@ describe("나만의 유튜브 강의실 전체 플로우 테스트", () => { cy.get(".modal-container").should("be.visible"); }); - /** - * youtube에 검색한 결과가 없는 경우를 찾기 어려움... (테스트 통과가 되지 않을 확률이 높다.) - */ - // it("보고싶은 영상 찾기 모달창 안에서 원하는 영상을 검색한 결과가 없는 경우 검색 결과 없음 이미지를 보여준다.", () => { - // cy.get("#search-input-keyword").type(errorSearchKeyword); - // cy.get("#search-button").click(); - // cy.get(".search-result--no-result").should("be.visible"); - // }); - - /** - * 실제 API 호출 했을 경우 주석을 제거 후 테스트를 돌려주세요. - */ - // it("보고싶은 영상 찾기 모달창 안에서 검색된 영상을 불러오는 동안 로딩 이미지를 보여준다.", () => { - // cy.get("#search-input-keyword").clear().type(searchKeyword); - // cy.get("#search-button").click(); - // cy.get(".skeleton").should("be.visible"); - // }); + it("보고싶은 영상 찾기 모달창 안에서 검색된 영상을 불러오는 동안 로딩 이미지를 보여준다.", () => { + cy.get("#search-input-keyword").clear().type(searchKeyword); + cy.get("#search-button").click(); + cy.get(".skeleton").should("be.visible"); + }); it("보고싶은 영상 찾기 모달창 안에서 검색창에 검색어를 입력하지 않으면 에러 메시지를 보여준다.", () => { cy.searchWithNoKeyword(); diff --git a/src/js/VideoStorage.js b/src/js/VideoStorage.js index be1e0b8d9..960a059db 100644 --- a/src/js/VideoStorage.js +++ b/src/js/VideoStorage.js @@ -10,9 +10,6 @@ export default class VideoStorage { } checkTypeVideoEmpty(isWatchedVideoOnly) { - console.log( - !this.getStorage().some((item) => item.isWatched === isWatchedVideoOnly) - ); return !this.getStorage().some( (item) => item.isWatched === isWatchedVideoOnly ); diff --git a/src/js/YoutubeApp.js b/src/js/YoutubeApp.js index ddb35de17..895601033 100644 --- a/src/js/YoutubeApp.js +++ b/src/js/YoutubeApp.js @@ -58,7 +58,6 @@ export default class YoutubeApp { } #reloadStorageData = () => { - console.log(this.videoStorage.getStorage()); if (this.videoStorage.checkTypeVideoEmpty(this.isWatchedVideoOnly)) { this.videoStorageView.renderEmptyStorage(); return; From b9fc0027932ad1705f3bc3fac2ef32207fd01846 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sat, 19 Mar 2022 22:05:26 +0900 Subject: [PATCH 24/37] =?UTF-8?q?feat:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fetch error 핸들링 추가 --- .eslintrc.json | 1 + src/js/YoutubeApp.js | 50 ++++++++++++++++++++--------------- src/js/api/getSearchResult.js | 5 +++- src/js/constants/constants.js | 2 +- src/js/utils/handleError.js | 11 ++++++++ 5 files changed, 46 insertions(+), 23 deletions(-) create mode 100644 src/js/utils/handleError.js diff --git a/.eslintrc.json b/.eslintrc.json index 248f09d27..10c183d89 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -17,6 +17,7 @@ "no-var": "error", "max-depth": ["error", 2], "no-alert": 0, + "default-case": 0, "no-console": "warn", "no-param-reassign": "error", "no-undefined": 0, diff --git a/src/js/YoutubeApp.js b/src/js/YoutubeApp.js index 895601033..e5a408714 100644 --- a/src/js/YoutubeApp.js +++ b/src/js/YoutubeApp.js @@ -166,32 +166,40 @@ export default class YoutubeApp { this.keyword = keyword; - const responseData = await getSearchResult(this.keyword); - this.nextPageToken = responseData.nextPageToken; - - if (responseData.items.length === 0) { - this.searchModalView.renderNoResultPage(); - return; + try { + const responseData = await getSearchResult(this.keyword); + this.nextPageToken = responseData.nextPageToken; + + if (responseData.items.length === 0) { + this.searchModalView.renderNoResultPage(); + return; + } + + this.searchModalView.renderSearchResult( + responseData, + this.videoStorage.getVideoIdArray() + ); + } catch { + this.searchModalView.unrenderSkeleton(); } - - this.searchModalView.renderSearchResult( - responseData, - this.videoStorage.getVideoIdArray() - ); } async searchNextPage() { this.searchModalView.renderSkeleton(); - const responseData = await getSearchResult( - this.keyword, - this.nextPageToken - ); - - this.nextPageToken = responseData.nextPageToken; - this.searchModalView.renderSearchResult( - responseData, - this.videoStorage.getVideoIdArray() - ); + try { + const responseData = await getSearchResult( + this.keyword, + this.nextPageToken + ); + + this.nextPageToken = responseData.nextPageToken; + this.searchModalView.renderSearchResult( + responseData, + this.videoStorage.getVideoIdArray() + ); + } catch { + this.searchModalView.unrenderSkeleton(); + } } } diff --git a/src/js/api/getSearchResult.js b/src/js/api/getSearchResult.js index e9c985292..5831a8112 100644 --- a/src/js/api/getSearchResult.js +++ b/src/js/api/getSearchResult.js @@ -1,5 +1,6 @@ import { ITEMS_PER_REQUEST, DEVELOP_MODE } from "../constants/constants"; import mockObject from "../mockObject"; +import handleError from "../utils/handleError"; export default async function getSearchResult( searchKeyword, @@ -32,12 +33,14 @@ export default async function getSearchResult( const data = await response.json(); if (!response.ok) { + console.log(data.error.name); + console.log(data.error.message); throw new Error(data.error.message); } return data; } catch (error) { - // TODO: 에러 메시지 핸들링 객체 만들어서 붙여주기 + handleError(error.message); return null; } } diff --git a/src/js/constants/constants.js b/src/js/constants/constants.js index 8e33d5412..4fdb285ec 100644 --- a/src/js/constants/constants.js +++ b/src/js/constants/constants.js @@ -10,4 +10,4 @@ export const ITEMS_PER_REQUEST = 10; export const ALLOCATE_FOR_RENDER_PX = 40; export const STORAGE_MAX_COUNT = 100; -export const DEVELOP_MODE = true; +export const DEVELOP_MODE = false; diff --git a/src/js/utils/handleError.js b/src/js/utils/handleError.js new file mode 100644 index 000000000..f41984f19 --- /dev/null +++ b/src/js/utils/handleError.js @@ -0,0 +1,11 @@ +const handleError = (errorMessage) => { + switch (errorMessage) { + case "Failed to fetch": + alert("인터넷 연결이 원활하지 않습니다. 잠시 후 다시 시도해주세요."); + break; + case "": + break; + } +}; + +export default handleError; From 7a47dd6fd8cee562f6b9fd55c2a5cdbc17bdeb95 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sat, 19 Mar 2022 22:34:01 +0900 Subject: [PATCH 25/37] =?UTF-8?q?feat:=20getSearchResult=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=EA=B0=80=20=EC=8B=A4=ED=96=89=EC=A4=91=EC=9D=BC?= =?UTF-8?q?=EB=95=8C=EB=8A=94=20=EC=A4=91=EB=B3=B5=20=EC=8B=A4=ED=96=89?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/YoutubeApp.js | 12 +++++++++++- src/js/api/getSearchResult.js | 10 ++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/js/YoutubeApp.js b/src/js/YoutubeApp.js index e5a408714..b917a9608 100644 --- a/src/js/YoutubeApp.js +++ b/src/js/YoutubeApp.js @@ -168,8 +168,13 @@ export default class YoutubeApp { try { const responseData = await getSearchResult(this.keyword); - this.nextPageToken = responseData.nextPageToken; + if (responseData === null) { + this.searchModalView.unrenderSkeleton(); + return; + } + + this.nextPageToken = responseData.nextPageToken; if (responseData.items.length === 0) { this.searchModalView.renderNoResultPage(); return; @@ -193,6 +198,11 @@ export default class YoutubeApp { this.nextPageToken ); + if (responseData === null) { + this.searchModalView.unrenderSkeleton(); + return; + } + this.nextPageToken = responseData.nextPageToken; this.searchModalView.renderSearchResult( responseData, diff --git a/src/js/api/getSearchResult.js b/src/js/api/getSearchResult.js index 5831a8112..42440f897 100644 --- a/src/js/api/getSearchResult.js +++ b/src/js/api/getSearchResult.js @@ -2,10 +2,16 @@ import { ITEMS_PER_REQUEST, DEVELOP_MODE } from "../constants/constants"; import mockObject from "../mockObject"; import handleError from "../utils/handleError"; +let isProgressing = false; + export default async function getSearchResult( searchKeyword, nextPageToken = "" ) { + if (isProgressing) { + return null; + } + if (DEVELOP_MODE) { return { items: mockObject(), @@ -13,6 +19,8 @@ export default async function getSearchResult( }; } + isProgressing = true; + const REDIRECT_SERVER_HOST = "https://unruffled-turing-aacdf7.netlify.app"; const url = new URL("youtube/v3/search", REDIRECT_SERVER_HOST); @@ -38,9 +46,11 @@ export default async function getSearchResult( throw new Error(data.error.message); } + isProgressing = false; return data; } catch (error) { handleError(error.message); + isProgressing = false; return null; } } From 452cac06049e4810b2d8404e6d4bb17e24fd8385 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Sat, 19 Mar 2022 22:34:42 +0900 Subject: [PATCH 26/37] =?UTF-8?q?feat:=20=EA=B2=80=EC=83=89=EB=90=9C=20?= =?UTF-8?q?=EC=98=81=EC=83=81=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=EC=9C=A0?= =?UTF-8?q?=ED=8A=9C=EB=B8=8C=EB=A1=9C=20=EB=B0=94=EB=A1=9C=EA=B0=80?= =?UTF-8?q?=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/css/index.css | 6 ++++++ src/js/templates.js | 40 ++++++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/css/index.css b/src/css/index.css index 598df7704..b5c17b520 100644 --- a/src/css/index.css +++ b/src/css/index.css @@ -45,6 +45,12 @@ h2 { font-size: 34px; } +a { + text-decoration: none; + color: #000000; + font: inherit; +} + @media (max-width: 1279px) { #app { max-width: 760px; diff --git a/src/js/templates.js b/src/js/templates.js index 70535bb4c..09d277ac7 100644 --- a/src/js/templates.js +++ b/src/js/templates.js @@ -27,16 +27,18 @@ const generateTemplate = { }, videoItem({ id, channel, thumbnail, title, date }, videoIdArray) { return `
    • - video-item-thumbnail -

      - ${title} -

      -

      ${channel}

      -

      ${date}

      + + video-item-thumbnail +

      + ${title} +

      +

      ${channel}

      +

      ${date}

      +
      -
    • `; - }, - videoItems(responseData, videoIdArray) { - return responseData - .map((item) => - this.videoItem( - { - id: item.id.videoId, - channel: item.snippet.channelTitle, - thumbnail: item.snippet.thumbnails.high.url, - title: item.snippet.title, - date: parsedDate(item.snippet.publishTime), - }, - videoIdArray - ) - ) - .join(""); - }, - savedVideoItem({ id, channel, thumbnail, title, date, isWatched }) { - return `
    • - - video-item-thumbnail -

      ${title}

      -

      ${channel}

      -

      ${date}

      -
      -
      - - -
      -
    • `; - }, - savedVideoItems(videoStorage, watchedVideoOnly) { - if (watchedVideoOnly) { - return videoStorage - .map((item) => { - return item.isWatched - ? this.savedVideoItem({ - id: item.videoId, - channel: item.channel, - thumbnail: item.thumbnail, - title: item.title, - date: item.publishTime, - isWatched: item.isWatched, - }) - : ""; - }) - .join(""); - } - - return videoStorage - .map((item) => { - return item.isWatched - ? "" - : this.savedVideoItem({ - id: item.videoId, - channel: item.channel, - thumbnail: item.thumbnail, - title: item.title, - date: item.publishTime, - isWatched: item.isWatched, - }); - }) - .join(""); - }, -}; - -export default generateTemplate; diff --git a/src/js/view/SearchModalView.js b/src/js/view/SearchModalView.js index 27a29bf64..ec055a1fb 100644 --- a/src/js/view/SearchModalView.js +++ b/src/js/view/SearchModalView.js @@ -1,5 +1,6 @@ import { scrollToTop } from "../utils/dom"; -import generateTemplate from "../templates"; +import { parsedDate } from "../utils/utils"; +import { ITEMS_PER_REQUEST } from "../constants/constants"; import notFountImage from "../../assets/images/not_found.png"; export default class SearchModalView { @@ -27,7 +28,7 @@ export default class SearchModalView { } renderSkeleton() { - this.videoList.insertAdjacentHTML("beforeend", generateTemplate.skeleton()); + this.videoList.insertAdjacentHTML("beforeend", this.#skeleton()); } unrenderSkeleton() { @@ -43,13 +44,13 @@ export default class SearchModalView { this.searchResult.classList.add("search-result--no-result"); this.searchResult.insertAdjacentHTML( "beforeend", - generateTemplate.noResult(notFountImage, message) + this.#noResult(notFountImage, message) ); } renderSearchResult(responseData, videoIdArray) { this.unrenderSkeleton(); - const videoItemTemplate = generateTemplate.videoItems( + const videoItemTemplate = this.#videoItems( responseData.items, videoIdArray ); @@ -60,4 +61,68 @@ export default class SearchModalView { hideSaveButton(target) { target.classList.add("hide"); } + + videoItem = ({ id, channel, thumbnail, title, date }, videoIdArray) => { + return `
    • + + video-item-thumbnail +

      + ${title} +

      +

      ${channel}

      +

      ${date}

      +
      + +
    • `; + }; + + #videoItems = (responseData, videoIdArray) => { + return responseData + .map((item) => + this.videoItem( + { + id: item.id.videoId, + channel: item.snippet.channelTitle, + thumbnail: item.snippet.thumbnails.high.url, + title: item.snippet.title, + date: parsedDate(item.snippet.publishTime), + }, + videoIdArray + ) + ) + .join(""); + }; + + #skeleton = () => { + return ` +
    • +
      +
      +
      +
      +
      +
      +
      +
    • + `.repeat(ITEMS_PER_REQUEST); + }; + + #noResult(src, message) { + return ` +
      + no result image +

      + ${message} +

      +
      + `; + } } diff --git a/src/js/view/VideoStorageView.js b/src/js/view/VideoStorageView.js index 7219209d3..8086557cc 100644 --- a/src/js/view/VideoStorageView.js +++ b/src/js/view/VideoStorageView.js @@ -1,4 +1,3 @@ -import generateTemplate from "../templates"; import notFountImage from "../../assets/images/not_found.png"; export default class VideoStorageView { @@ -8,6 +7,61 @@ export default class VideoStorageView { this.noResultDiv = document.querySelector(".no-result"); } + savedVideoItem = ({ id, channel, thumbnail, title, date, isWatched }) => { + return `
    • + + video-item-thumbnail +

      ${title}

      +

      ${channel}

      +

      ${date}

      +
      +
      + + +
      +
    • `; + }; + + #savedVideoItems = (videoStorage, watchedVideoOnly) => { + if (watchedVideoOnly) { + return videoStorage + .map((item) => { + return item.isWatched + ? this.savedVideoItem({ + id: item.videoId, + channel: item.channel, + thumbnail: item.thumbnail, + title: item.title, + date: item.publishTime, + isWatched: item.isWatched, + }) + : ""; + }) + .join(""); + } + + return videoStorage + .map((item) => { + return item.isWatched + ? "" + : this.savedVideoItem({ + id: item.videoId, + channel: item.channel, + thumbnail: item.thumbnail, + title: item.title, + date: item.publishTime, + isWatched: item.isWatched, + }); + }) + .join(""); + }; + renderEmptyStorage = () => { this.savedVideoList.classList.add("hide"); this.noResultDiv.classList.remove("hide"); @@ -15,7 +69,7 @@ export default class VideoStorageView { }; renderSavedVideo = (videoData, watchedVideoOnly) => { - const videoItemTemplate = generateTemplate.savedVideoItems( + const videoItemTemplate = this.#savedVideoItems( videoData, watchedVideoOnly ); From f238a3ecb95a33611146bb3110c034195a5ae04f Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Mon, 21 Mar 2022 02:09:46 +0900 Subject: [PATCH 31/37] =?UTF-8?q?refactor:=20searchModal=EC=9D=98=20no-res?= =?UTF-8?q?ult=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=91=9C=EC=8B=9C=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=EC=9D=84=20hide=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80/=EC=A0=9C=EA=B1=B0=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 6 ++++++ src/js/view/SearchModalView.js | 13 +++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index 45a6d1874..8cebe6509 100644 --- a/index.html +++ b/index.html @@ -67,6 +67,12 @@
        +
        + no result image +

        + 검색 결과가 없습니다
        다른 키워드로 검색해보세요 +

        +
        diff --git a/src/js/view/SearchModalView.js b/src/js/view/SearchModalView.js index ec055a1fb..60c97a55b 100644 --- a/src/js/view/SearchModalView.js +++ b/src/js/view/SearchModalView.js @@ -9,6 +9,10 @@ export default class SearchModalView { this.searchInputKeyword = document.querySelector("#search-input-keyword"); this.searchResult = document.querySelector(".search-result"); this.videoList = document.querySelector(".video-list"); + this.noResultDiv = document.querySelector(".search-result .no-result"); + this.noResultImage = document.querySelector( + "#search-modal-no-result__image" + ); } openSearchModal() { @@ -38,18 +42,15 @@ export default class SearchModalView { } renderNoResultPage() { - const message = "검색 결과가 없습니다
        다른 키워드로 검색해보세요"; - this.searchResult.removeChild(this.videoList); this.searchResult.classList.add("search-result--no-result"); - this.searchResult.insertAdjacentHTML( - "beforeend", - this.#noResult(notFountImage, message) - ); + this.noResultDiv.classList.remove("hide"); + this.noResultImage.src = notFountImage; } renderSearchResult(responseData, videoIdArray) { this.unrenderSkeleton(); + this.noResultDiv.classList.add("hide"); const videoItemTemplate = this.#videoItems( responseData.items, videoIdArray From 98c5a19c3dff418f4a30ad8ff3e171a2fd2e3323 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Mon, 21 Mar 2022 02:13:33 +0900 Subject: [PATCH 32/37] =?UTF-8?q?refactor:=20handleError=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EB=A5=BC=20api=20=EB=82=B4=EB=B6=80=EB=A1=9C?= =?UTF-8?q?=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/api/getSearchResult.js | 10 ++++++++-- src/js/utils/handleError.js | 11 ----------- 2 files changed, 8 insertions(+), 13 deletions(-) delete mode 100644 src/js/utils/handleError.js diff --git a/src/js/api/getSearchResult.js b/src/js/api/getSearchResult.js index a717eaff4..a5c2acebb 100644 --- a/src/js/api/getSearchResult.js +++ b/src/js/api/getSearchResult.js @@ -1,6 +1,5 @@ import { ITEMS_PER_REQUEST, DEVELOP_MODE } from "../constants/constants"; import mockObject from "../mockObject"; -import handleError from "../utils/handleError"; let isProgressing = false; @@ -47,7 +46,14 @@ export default async function getSearchResult( isProgressing = false; return data; } catch (error) { - handleError(error.message); + switch (error.message) { + case "Failed to fetch": + alert("인터넷 연결이 원활하지 않습니다. 잠시 후 다시 시도해주세요."); + break; + case "": + break; + } + isProgressing = false; return null; } diff --git a/src/js/utils/handleError.js b/src/js/utils/handleError.js deleted file mode 100644 index f41984f19..000000000 --- a/src/js/utils/handleError.js +++ /dev/null @@ -1,11 +0,0 @@ -const handleError = (errorMessage) => { - switch (errorMessage) { - case "Failed to fetch": - alert("인터넷 연결이 원활하지 않습니다. 잠시 후 다시 시도해주세요."); - break; - case "": - break; - } -}; - -export default handleError; From 4cde15674a9f35e2d1344dd40a70890dc442f16c Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Mon, 21 Mar 2022 02:48:25 +0900 Subject: [PATCH 33/37] =?UTF-8?q?refactor:=20VideoStorage=EC=9D=98=20getSt?= =?UTF-8?q?orage()=EB=A5=BC=20getVideos()=20=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 7 +++++- src/js/VideoStorage.js | 32 +++++++++++++-------------- src/js/YoutubeApp.js | 2 +- src/js/__tests__/videoStorage.test.js | 2 +- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/index.html b/index.html index 8cebe6509..8c2fe4d2a 100644 --- a/index.html +++ b/index.html @@ -68,7 +68,12 @@
          - no result image + no result image

          검색 결과가 없습니다
          다른 키워드로 검색해보세요

          diff --git a/src/js/VideoStorage.js b/src/js/VideoStorage.js index 960a059db..83dd6868f 100644 --- a/src/js/VideoStorage.js +++ b/src/js/VideoStorage.js @@ -1,52 +1,50 @@ import { ERROR_MESSAGE, STORAGE_MAX_COUNT } from "./constants/constants"; export default class VideoStorage { - constructor() { - this.videos = JSON.parse(localStorage.getItem("videos")) || []; - } + #videos = JSON.parse(localStorage.getItem("videos")) || []; isSavedVideoId(responseId) { - return this.videos.includes(responseId); + return this.#videos.includes(responseId); } checkTypeVideoEmpty(isWatchedVideoOnly) { - return !this.getStorage().some( + return !this.getVideos().some( (item) => item.isWatched === isWatchedVideoOnly ); } addVideoData(data) { - if (this.videos.length >= STORAGE_MAX_COUNT) { + if (this.#videos.length >= STORAGE_MAX_COUNT) { throw new Error(ERROR_MESSAGE.VIDEO_STORAGE_OVERFLOW); } - this.videos = [...this.videos, data]; - localStorage.setItem("videos", JSON.stringify(this.videos)); + this.#videos = [...this.#videos, data]; + localStorage.setItem("videos", JSON.stringify(this.#videos)); } - getStorage() { - return this.videos; + getVideos() { + return this.#videos; } getVideoIdArray() { - return this.getStorage().map((item) => item.videoId); + return this.getVideos().map((item) => item.videoId); } setVideoStateWatched(target) { - const targetIndex = this.getStorage().findIndex( + const targetIndex = this.getVideos().findIndex( (item) => item.videoId === target ); - this.videos[targetIndex].isWatched = !this.videos[targetIndex].isWatched; - localStorage.setItem("videos", JSON.stringify(this.videos)); + this.#videos[targetIndex].isWatched = !this.#videos[targetIndex].isWatched; + localStorage.setItem("videos", JSON.stringify(this.#videos)); } deleteVideo(target) { - const targetIndex = this.getStorage().findIndex( + const targetIndex = this.getVideos().findIndex( (item) => item.videoId === target ); - this.videos.splice(targetIndex, 1); - localStorage.setItem("videos", JSON.stringify(this.videos)); + this.#videos.splice(targetIndex, 1); + localStorage.setItem("videos", JSON.stringify(this.#videos)); } } diff --git a/src/js/YoutubeApp.js b/src/js/YoutubeApp.js index b917a9608..c2a677153 100644 --- a/src/js/YoutubeApp.js +++ b/src/js/YoutubeApp.js @@ -64,7 +64,7 @@ export default class YoutubeApp { } this.videoStorageView.renderSavedVideo( - this.videoStorage.getStorage(), + this.videoStorage.getVideos(), this.isWatchedVideoOnly ); }; diff --git a/src/js/__tests__/videoStorage.test.js b/src/js/__tests__/videoStorage.test.js index 3b23941a0..eb25ea978 100644 --- a/src/js/__tests__/videoStorage.test.js +++ b/src/js/__tests__/videoStorage.test.js @@ -8,7 +8,7 @@ describe("VideoStorage에 동영상의 데이터가 적절히 저장되어야 const testData = { id: 123, title: "테스트" }; videoStorage.addVideoData(testData); - expect(videoStorage.getStorage().includes(testData)).toBe(true); + expect(videoStorage.getVideos().includes(testData)).toBe(true); }); test("VideoStorage에 101개 이상의 데이터가 저장되면 에러 메시지를 반환한다.", () => { From 592d52ea674c2a432168cd21335100438ad9b7ad Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Mon, 21 Mar 2022 02:50:36 +0900 Subject: [PATCH 34/37] =?UTF-8?q?fix:=20=EC=82=AC=EC=9A=A9=EB=90=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8D=98=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?isSavedVideoId=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/VideoStorage.js | 4 ---- src/js/__tests__/videoStorage.test.js | 22 ---------------------- 2 files changed, 26 deletions(-) diff --git a/src/js/VideoStorage.js b/src/js/VideoStorage.js index 83dd6868f..879ce5ef2 100644 --- a/src/js/VideoStorage.js +++ b/src/js/VideoStorage.js @@ -3,10 +3,6 @@ import { ERROR_MESSAGE, STORAGE_MAX_COUNT } from "./constants/constants"; export default class VideoStorage { #videos = JSON.parse(localStorage.getItem("videos")) || []; - isSavedVideoId(responseId) { - return this.#videos.includes(responseId); - } - checkTypeVideoEmpty(isWatchedVideoOnly) { return !this.getVideos().some( (item) => item.isWatched === isWatchedVideoOnly diff --git a/src/js/__tests__/videoStorage.test.js b/src/js/__tests__/videoStorage.test.js index eb25ea978..933480624 100644 --- a/src/js/__tests__/videoStorage.test.js +++ b/src/js/__tests__/videoStorage.test.js @@ -23,25 +23,3 @@ describe("VideoStorage에 동영상의 데이터가 적절히 저장되어야 ).toThrowError(ERROR_MESSAGE.VIDEO_STORAGE_OVERFLOW); }); }); - -describe("이미 저장된 videoId는 다시 저장될 수 없다.", () => { - const responseId = "kkojaeId"; - - beforeEach(() => { - localStorage.clear(); - }); - - test("이미 저장된 videoId이면 true를 반환한다.", () => { - const videoStorage = new VideoStorage(); - videoStorage.addVideoData("kkojaeId"); - - expect(videoStorage.isSavedVideoId(responseId)).toBe(true); - }); - - test("저장된 videoId가 아니면 false를 반환한다.", () => { - const videoStorage = new VideoStorage(); - videoStorage.addVideoData("usageId"); - - expect(videoStorage.isSavedVideoId(responseId)).toBe(false); - }); -}); From 45c75c29a0bd30a59860ab85d9c62b6d0eeac2ad Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Mon, 21 Mar 2022 03:05:14 +0900 Subject: [PATCH 35/37] =?UTF-8?q?chore:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/__tests__/app.test.js | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 src/js/__tests__/app.test.js diff --git a/src/js/__tests__/app.test.js b/src/js/__tests__/app.test.js deleted file mode 100644 index e4bef0b98..000000000 --- a/src/js/__tests__/app.test.js +++ /dev/null @@ -1,33 +0,0 @@ -// import getSearchResult from "../api/getSearchResult"; -// 테스트 케이스 오류를 막기 위해 작성해놓은 코드 입니다. -test("", () => {}); - -/** - * 실제 API를 호출하지 않고 Mock Data를 이용하여 테스트하는 케이스는 불필요하다고 생각되는데... - * 리뷰어님 생각은 어떠신지 궁금하네용 😥 - * 실제 API 통신을 테스트 할 때는 어떻게 하는지 알 수 있을까요? - */ - -// test("최초 검색 결과는 10개까지만 보여준다.", async () => { -// fetch.mockResponseOnce( -// JSON.stringify([ -// { -// id: { -// videoId: 1, -// }, -// snippet: { -// channelTitle: "essential;", -// thumbnails: { -// high: { -// url: "https://i.ytimg.com/vi/ECfuKi5-Cfs/hq720.jpg?sqp=-oaymwEcCOgCEMoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDvmIcX-TgdcH2g_Bd4AUxw6hjmvQ", -// }, -// }, -// publishTime: "2022-03-02T11:39:31Z", -// title: "[Playlist] 너무 좋은데 괜찮으시겠어요?", -// }, -// }, -// ]) -// ); -// const result = await getSearchResult("xooos"); -// expect(result.length).toBe(1); -// }); From bb3cfcb49c5f78ede147ac9b7337d5c99f2733bc Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Mon, 21 Mar 2022 03:21:34 +0900 Subject: [PATCH 36/37] =?UTF-8?q?fix:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=9E=AC=EA=B2=80=EC=83=89=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cypress/integration/app.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/cypress/integration/app.test.js b/cypress/integration/app.test.js index 7294e2fba..cae9f5368 100644 --- a/cypress/integration/app.test.js +++ b/cypress/integration/app.test.js @@ -44,7 +44,6 @@ describe("나만의 유튜브 강의실 전체 플로우 테스트", () => { }); it("보고싶은 영상 찾기 모달창 안에서 검색된 영상이 이미 저장된 영상이라면 저장 버튼이 보이지 않아야 한다.", () => { - cy.searchWithKeyword(searchKeyword); cy.get(".video-item__save-button").eq(0).should("be.not.visible"); }); From 84faf8be7692fcadf79fd541ab6273e004ac3fc5 Mon Sep 17 00:00:00 2001 From: "YongRae_Kim (Usage)" Date: Mon, 21 Mar 2022 03:41:21 +0900 Subject: [PATCH 37/37] =?UTF-8?q?refactor:=20innerHTML=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EC=9D=98=20=EB=8C=80=EC=B2=B4=EB=A1=9C=20?= =?UTF-8?q?=EA=B9=9C=EB=B9=A1=EC=9E=84=20=ED=98=84=EC=83=81=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/view/SearchModalView.js | 10 +++++++--- src/js/view/VideoStorageView.js | 10 ++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/js/view/SearchModalView.js b/src/js/view/SearchModalView.js index 60c97a55b..d5380f536 100644 --- a/src/js/view/SearchModalView.js +++ b/src/js/view/SearchModalView.js @@ -8,7 +8,7 @@ export default class SearchModalView { this.modalContainer = document.querySelector(".modal-container"); this.searchInputKeyword = document.querySelector("#search-input-keyword"); this.searchResult = document.querySelector(".search-result"); - this.videoList = document.querySelector(".video-list"); + this.videoList = document.querySelector(".search-result .video-list"); this.noResultDiv = document.querySelector(".search-result .no-result"); this.noResultImage = document.querySelector( "#search-modal-no-result__image" @@ -22,13 +22,17 @@ export default class SearchModalView { closeSearchModal() { this.searchInputKeyword.value = ""; scrollToTop(this.videoList); - this.videoList.innerHTML = ""; + document + .querySelectorAll(".search-result .video-item") + .forEach((element) => element.remove()); this.modalContainer.classList.add("hide"); } clearVideoList() { scrollToTop(this.videoList); - this.videoList.innerHTML = ""; + document + .querySelectorAll(".search-result .video-item") + .forEach((element) => element.remove()); } renderSkeleton() { diff --git a/src/js/view/VideoStorageView.js b/src/js/view/VideoStorageView.js index 8086557cc..322781789 100644 --- a/src/js/view/VideoStorageView.js +++ b/src/js/view/VideoStorageView.js @@ -4,7 +4,9 @@ export default class VideoStorageView { constructor() { this.savedVideoSection = document.querySelector(".saved-video__section"); this.savedVideoList = document.querySelector(".saved-video-list"); - this.noResultDiv = document.querySelector(".no-result"); + this.noResultDiv = document.querySelector( + ".saved-video__section .no-result" + ); } savedVideoItem = ({ id, channel, thumbnail, title, date, isWatched }) => { @@ -74,8 +76,12 @@ export default class VideoStorageView { watchedVideoOnly ); + document + .querySelectorAll(".saved-video-list .video-item") + .forEach((element) => element.remove()); + this.savedVideoList.classList.remove("hide"); - this.savedVideoList.innerHTML = videoItemTemplate; + this.savedVideoList.insertAdjacentHTML("beforeend", videoItemTemplate); if (!this.noResultDiv) { return;