From 5aadd5e46124cdcea0ef6e3f6cadd5f33467f66a Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Wed, 22 Mar 2023 14:52:08 +0900 Subject: [PATCH 01/38] =?UTF-8?q?feat:=20=EB=AC=B4=ED=95=9C=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=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 --- index.html | 2 +- src/components/MovieList/index.ts | 40 +++++++++++++++++++++++-------- templates/common.css | 3 +-- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/index.html b/index.html index 250b4576e..b6c486869 100644 --- a/index.html +++ b/index.html @@ -13,7 +13,7 @@

지금 인기 있는 영화

- +
diff --git a/src/components/MovieList/index.ts b/src/components/MovieList/index.ts index 2baa051cc..5879f1e57 100644 --- a/src/components/MovieList/index.ts +++ b/src/components/MovieList/index.ts @@ -33,14 +33,18 @@ export class MovieList { fetchPopularMovies(this.#state.page) .then((response) => { const { results, total_pages } = response; + this.#movies.reset(results); this.render(results, total_pages); + + const observer = new IntersectionObserver( + this.onClickMoreButton.bind(this) + ); + observer.observe($(".btn")); }) .catch(() => { this.#$target.removeChild(this.#$skeletonContainer); }); - - $(".btn").addEventListener("click", this.onClickMoreButton.bind(this)); } getMovieCardTemplate(movie: Movie) { @@ -50,14 +54,27 @@ export class MovieList {
  • - ${movie.title} + ${ + movie.poster_path + ? /*html */ ` + ${movie.title} + ` + : /*html */ ` +
    + No Image +
    + ` + }

    ${movie.title}

    -

    별점 ${movie.vote_average}${movie.vote_average}

    +

    + 별점 ${movie.vote_average} + ${movie.vote_average} +

  • @@ -75,9 +92,12 @@ export class MovieList { `${this.#movies .getCurrentList() .map((movie) => this.getMovieCardTemplate(movie)) - .join("")}` + .join("")} + ` ); + console.log("render"); + if (this.#state.page === total_pages) this.hideMoreButton(); } diff --git a/templates/common.css b/templates/common.css index b22b31a29..64dfde80a 100644 --- a/templates/common.css +++ b/templates/common.css @@ -94,11 +94,10 @@ button { margin-top: 48px; } -button.btn { +btn { border: 0; border-radius: 8px; height: 30px; - color: #fff; } button.primary { From 1cf533a2642605999131b24ef8622ec26756ef6a Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Wed, 22 Mar 2023 15:16:24 +0900 Subject: [PATCH 02/38] =?UTF-8?q?refactor:=20=EB=A1=9C=EB=94=A9=20?= =?UTF-8?q?=EC=8A=A4=EC=BC=88=EB=A0=88=ED=86=A4=20=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=20(=EC=9A=94=EC=86=8C=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20=EB=B0=A9=EC=8B=9D=20=3D>=20display=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/MovieList/MovieCard.ts | 38 +++++++++++ src/components/MovieList/Skeleton.ts | 34 ++++++++++ src/components/MovieList/index.ts | 91 +++++++++------------------ src/components/Skeleton/index.ts | 18 ------ 4 files changed, 101 insertions(+), 80 deletions(-) create mode 100644 src/components/MovieList/MovieCard.ts create mode 100644 src/components/MovieList/Skeleton.ts delete mode 100644 src/components/Skeleton/index.ts diff --git a/src/components/MovieList/MovieCard.ts b/src/components/MovieList/MovieCard.ts new file mode 100644 index 000000000..f8349f55d --- /dev/null +++ b/src/components/MovieList/MovieCard.ts @@ -0,0 +1,38 @@ +import { Movie } from "../../types"; + +import starImg from "../../../templates/star_filled.png"; + +export const getMovieCardTemplate = (movie: Movie) => { + return ( + /*html*/ + ` +
  • + +
    + ${ + movie.poster_path + ? /*html */ ` + ${movie.title} + ` + : /*html */ ` +
    + No Image +
    + ` + } +

    ${movie.title}

    +

    + 별점 ${movie.vote_average} + ${movie.vote_average} +

    +
    +
    +
  • + ` + ); +}; diff --git a/src/components/MovieList/Skeleton.ts b/src/components/MovieList/Skeleton.ts new file mode 100644 index 000000000..8b3a3870b --- /dev/null +++ b/src/components/MovieList/Skeleton.ts @@ -0,0 +1,34 @@ +import { $ } from "../../utils/selector"; + +export const getSkeletonContainer = () => { + const skeletonContainer = document.createElement("ul"); + + skeletonContainer.className = "item-list skeleton-container"; + skeletonContainer.innerHTML = /*html*/ ` +
  • + +
    +
    +
    +
    +
    +
    +
  • + `.repeat(20); + + return skeletonContainer; +}; + +export const deleteSkeletonContainer = () => { + const skeletonContainer = $(".skeleton-container"); + + if (skeletonContainer instanceof HTMLElement) + skeletonContainer.style.display = "none"; +}; + +export const showSkeletonContainer = () => { + const skeletonContainer = $(".skeleton-container"); + + if (skeletonContainer instanceof HTMLElement) + skeletonContainer.style.display = "grid"; +}; diff --git a/src/components/MovieList/index.ts b/src/components/MovieList/index.ts index 5879f1e57..5d2cebc8d 100644 --- a/src/components/MovieList/index.ts +++ b/src/components/MovieList/index.ts @@ -1,9 +1,13 @@ import { Movies } from "../../domain/Movies"; -import { Movie, MovieResponse } from "../../types"; +import { MovieResponse } from "../../types"; import { fetchPopularMovies, fetchSearchMovies } from "../../utils/api"; -import starImg from "../../../templates/star_filled.png"; import { $ } from "../../utils/selector"; -import { getSkeletonContainer } from "../Skeleton"; +import { + deleteSkeletonContainer, + getSkeletonContainer, + showSkeletonContainer, +} from "./Skeleton"; +import { getMovieCardTemplate } from "./MovieCard"; type showType = "popular" | "search"; @@ -24,11 +28,14 @@ export class MovieList { #movies: Movies = new Movies([]); - #$skeletonContainer = getSkeletonContainer(); - constructor($target: Element) { this.#$target = $target; - this.renderSkeleton(); + + this.init(); + } + + init() { + this.#$target.insertAdjacentElement("afterend", getSkeletonContainer()); fetchPopularMovies(this.#state.page) .then((response) => { @@ -37,53 +44,17 @@ export class MovieList { this.#movies.reset(results); this.render(results, total_pages); - const observer = new IntersectionObserver( - this.onClickMoreButton.bind(this) + new IntersectionObserver(this.fetchNextPage.bind(this)).observe( + $(".btn") ); - observer.observe($(".btn")); }) .catch(() => { - this.#$target.removeChild(this.#$skeletonContainer); + deleteSkeletonContainer(); }); } - getMovieCardTemplate(movie: Movie) { - return ( - /*html*/ - ` -
  • - -
    - ${ - movie.poster_path - ? /*html */ ` - ${movie.title} - ` - : /*html */ ` -
    - No Image -
    - ` - } -

    ${movie.title}

    -

    - 별점 ${movie.vote_average} - ${movie.vote_average} -

    -
    -
    -
  • - ` - ); - } - render(movieList: MovieResponse[], total_pages: number) { - this.#$target.removeChild(this.#$skeletonContainer); + deleteSkeletonContainer(); if (this.#state.page !== 1) this.#movies.add(movieList); @@ -91,22 +62,22 @@ export class MovieList { "beforeend", `${this.#movies .getCurrentList() - .map((movie) => this.getMovieCardTemplate(movie)) + .map((movie) => getMovieCardTemplate(movie)) .join("")} ` ); console.log("render"); - if (this.#state.page === total_pages) this.hideMoreButton(); + if (this.#state.page === total_pages) this.deactivateScrollFetch(); } reset(state: showType, searchKeyword?: string) { this.#$target.innerHTML = ``; this.#state = { ...this.#state, showState: state, page: 1 }; - this.showMoreButton(); - this.renderSkeleton(); + this.activateScrollFetch(); + showSkeletonContainer(); if (state === "popular") { fetchPopularMovies(this.#state.page) @@ -117,7 +88,7 @@ export class MovieList { this.render(results, total_pages); }) .catch(() => { - this.#$target.removeChild(this.#$skeletonContainer); + deleteSkeletonContainer(); }); return; @@ -134,14 +105,14 @@ export class MovieList { this.render(results, total_pages); }) .catch(() => { - this.#$target.removeChild(this.#$skeletonContainer); + deleteSkeletonContainer(); }); } } - onClickMoreButton() { + fetchNextPage() { this.#state.page += 1; - this.renderSkeleton(); + showSkeletonContainer(); if (this.#state.showState === "popular") fetchPopularMovies(this.#state.page) @@ -151,7 +122,7 @@ export class MovieList { this.render(results, total_pages); }) .catch(() => { - this.#$target.removeChild(this.#$skeletonContainer); + deleteSkeletonContainer(); }); if (this.#state.showState === "search") @@ -162,19 +133,15 @@ export class MovieList { this.render(results, total_pages); }) .catch(() => { - this.#$target.removeChild(this.#$skeletonContainer); + deleteSkeletonContainer(); }); } - renderSkeleton() { - this.#$target.appendChild(this.#$skeletonContainer); - } - - hideMoreButton() { + deactivateScrollFetch() { $(".btn").setAttribute("hidden", ""); } - showMoreButton() { + activateScrollFetch() { $(".btn").removeAttribute("hidden"); } } diff --git a/src/components/Skeleton/index.ts b/src/components/Skeleton/index.ts deleted file mode 100644 index 231130ee2..000000000 --- a/src/components/Skeleton/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const getSkeletonContainer = () => { - const skeletonContainer = document.createElement("ul"); - skeletonContainer.className = "item-list"; - /*html*/ - skeletonContainer.innerHTML = ` -
  • - -
    -
    -
    -
    -
    -
    -
  • - `.repeat(20); - - return skeletonContainer; -}; From 36ef1e966983356066ddd385c865c4facf4e55a1 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Wed, 22 Mar 2023 16:23:51 +0900 Subject: [PATCH 03/38] =?UTF-8?q?feat:=20=EB=B0=98=EC=9D=91=ED=98=95=20?= =?UTF-8?q?=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.ts | 5 +- src/components/Header/index.css | 27 ++++++ src/components/Header/index.ts | 8 ++ src/components/MovieList/index.ts | 6 +- .../components/MovieList/movieList.css | 84 +++++-------------- src/index.js | 4 +- src/style/common.css | 38 +++++++++ {templates => src/style}/reset.css | 0 8 files changed, 104 insertions(+), 68 deletions(-) create mode 100644 src/components/Header/index.css rename templates/common.css => src/components/MovieList/movieList.css (60%) create mode 100644 src/style/common.css rename {templates => src/style}/reset.css (100%) diff --git a/src/App.ts b/src/App.ts index 7581082c3..63657b2d8 100644 --- a/src/App.ts +++ b/src/App.ts @@ -25,7 +25,7 @@ export class App { subTitle.innerHTML = `"${serachKeyword}" 검색 결과`; if (this.#movieList instanceof MovieList) - this.#movieList.reset("search", serachKeyword); + this.#movieList.changeShowTarget("search", serachKeyword); } onClickLogoImage() { @@ -33,6 +33,7 @@ export class App { subTitle.innerHTML = `지금 인기 있는 영화`; - if (this.#movieList instanceof MovieList) this.#movieList.reset("popular"); + if (this.#movieList instanceof MovieList) + this.#movieList.changeShowTarget("popular"); } } diff --git a/src/components/Header/index.css b/src/components/Header/index.css new file mode 100644 index 000000000..ed60ab0a1 --- /dev/null +++ b/src/components/Header/index.css @@ -0,0 +1,27 @@ +header h1 { + cursor: pointer; + user-select: none; + font-size: 2rem; + font-weight: bold; + letter-spacing: -0.1rem; + color: #f33f3f; +} + +header > .search-box { + background: #fff; + padding: 8px; + border-radius: 4px; +} + +header .search-box > input { + border: 0; +} + +header .search-box > .search-button { + width: 14px; + border: 0; + text-indent: -1000rem; + background: url("../../../templates/search_button.png") transparent no-repeat + 0 1px; + background-size: contain; +} diff --git a/src/components/Header/index.ts b/src/components/Header/index.ts index 590d30227..3901a10b5 100644 --- a/src/components/Header/index.ts +++ b/src/components/Header/index.ts @@ -1,3 +1,5 @@ +import "./index.css"; + import logoImage from "../../../templates/logo.png"; import { $ } from "../../utils/selector"; @@ -12,7 +14,13 @@ export class Header { this.#$target = $target; this.render(); + this.bindEvent(onSubmitSearchKeyword, onClickLogoImage); + } + bindEvent( + onSubmitSearchKeyword: (searchKeyword: string) => void, + onClickLogoImage: () => void + ) { $(".search-box").addEventListener("submit", (event: Event) => { event.preventDefault(); diff --git a/src/components/MovieList/index.ts b/src/components/MovieList/index.ts index 5d2cebc8d..d8350a0e1 100644 --- a/src/components/MovieList/index.ts +++ b/src/components/MovieList/index.ts @@ -1,3 +1,5 @@ +import "./movieList.css"; + import { Movies } from "../../domain/Movies"; import { MovieResponse } from "../../types"; import { fetchPopularMovies, fetchSearchMovies } from "../../utils/api"; @@ -67,12 +69,10 @@ export class MovieList { ` ); - console.log("render"); - if (this.#state.page === total_pages) this.deactivateScrollFetch(); } - reset(state: showType, searchKeyword?: string) { + changeShowTarget(state: showType, searchKeyword?: string) { this.#$target.innerHTML = ``; this.#state = { ...this.#state, showState: state, page: 1 }; diff --git a/templates/common.css b/src/components/MovieList/movieList.css similarity index 60% rename from templates/common.css rename to src/components/MovieList/movieList.css index 64dfde80a..4f9bf459f 100644 --- a/templates/common.css +++ b/src/components/MovieList/movieList.css @@ -1,29 +1,3 @@ -* { - box-sizing: border-box !important; -} - -body { - font-size: 14px; - background-color: #222222; - color: #fff; -} - -a { - color: inherit; - text-decoration: none; -} - -button { - cursor: pointer; -} - -#app { - padding-bottom: 48px; -} - -*:focus { - outline: none; -} .item-view, .item-test { width: 100%; @@ -122,42 +96,30 @@ button.primary { background-position: 0% 50%; } } -header { - width: 100%; - min-width: 1200px; - height: 72px; - background-color: #222; - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 20px; - border-bottom: 1px solid white; - margin-bottom: 48px; -} - -header h1 { - cursor: pointer; - user-select: none; - font-size: 2rem; - font-weight: bold; - letter-spacing: -0.1rem; - color: #f33f3f; -} -header > .search-box { - background: #fff; - padding: 8px; - border-radius: 4px; -} - -header .search-box > input { - border: 0; +@media screen and (max-width: 1200px) { + .item-view h2 { + font-size: 28px; + } + .item-view { + width: 674px; + } + .item-list { + grid-template-columns: repeat(3, 180px); + grid-column-gap: 64px; + } } -header .search-box > .search-button { - width: 14px; - border: 0; - text-indent: -1000rem; - background: url("./search_button.png") transparent no-repeat 0 1px; - background-size: contain; +@media screen and (max-width: 674px) { + .item-view { + width: max-content; + } + .item-list { + grid-template-columns: repeat(2, 140px); + grid-column-gap: 36px; + } + .item-thumbnail { + width: 140px; + height: 220px; + } } diff --git a/src/index.js b/src/index.js index f71cf8f8d..bdbb89657 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ import { App } from "./App"; -import "../templates/reset.css"; -import "../templates/common.css"; +import "./style/reset.css"; +import "./style/common.css"; const app = new App(); diff --git a/src/style/common.css b/src/style/common.css new file mode 100644 index 000000000..1abd4a267 --- /dev/null +++ b/src/style/common.css @@ -0,0 +1,38 @@ +* { + box-sizing: border-box !important; +} + +body { + font-size: 14px; + background-color: #222222; + color: #fff; +} + +a { + color: inherit; + text-decoration: none; +} + +button { + cursor: pointer; +} + +#app { + padding-bottom: 48px; +} + +*:focus { + outline: none; +} + +header { + width: 100%; + height: 72px; + background-color: #222; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 20px; + border-bottom: 1px solid white; + margin-bottom: 48px; +} diff --git a/templates/reset.css b/src/style/reset.css similarity index 100% rename from templates/reset.css rename to src/style/reset.css From a0c58190cfec42bb7d0c2033f94e80c6860f6e9e Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Wed, 22 Mar 2023 17:05:30 +0900 Subject: [PATCH 04/38] =?UTF-8?q?feat:=20=EC=84=A0=ED=83=9D=EB=90=9C=20?= =?UTF-8?q?=EC=98=81=ED=99=94=20=EC=A0=95=EB=B3=B4=20=EB=B6=88=EB=9F=AC?= =?UTF-8?q?=EC=98=A4=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/App.ts | 18 +++++++++ src/components/MovieList/MovieCard.ts | 56 +++++++++++++-------------- src/components/MovieList/index.ts | 7 +++- src/domain/Movies.ts | 9 +++++ 4 files changed, 61 insertions(+), 29 deletions(-) diff --git a/src/App.ts b/src/App.ts index 63657b2d8..6c6899951 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,5 +1,6 @@ import { Header } from "./components/Header"; import { MovieList } from "./components/MovieList"; +import { Movie } from "./types"; import { $ } from "./utils/selector"; export class App { @@ -17,6 +18,19 @@ export class App { ); this.#movieList = new MovieList($movieList); + + this.bindEvent(); + } + + bindEvent() { + window.addEventListener("popstate", () => { + const selectedMovieId = Number(window.location.hash.replace("#", "")); + + this.#movieList.getMovieInfo( + selectedMovieId, + this.onClickMovieCard.bind(this) + ); + }); } onSubmitSearchKeyword(serachKeyword: string) { @@ -36,4 +50,8 @@ export class App { if (this.#movieList instanceof MovieList) this.#movieList.changeShowTarget("popular"); } + + onClickMovieCard(movie: Movie) { + console.log(movie); + } } diff --git a/src/components/MovieList/MovieCard.ts b/src/components/MovieList/MovieCard.ts index f8349f55d..2475866a9 100644 --- a/src/components/MovieList/MovieCard.ts +++ b/src/components/MovieList/MovieCard.ts @@ -6,33 +6,33 @@ export const getMovieCardTemplate = (movie: Movie) => { return ( /*html*/ ` -
  • - -
    - ${ - movie.poster_path - ? /*html */ ` - ${movie.title} - ` - : /*html */ ` -
    - No Image -
    - ` - } -

    ${movie.title}

    -

    - 별점 ${movie.vote_average} - ${movie.vote_average} -

    -
    -
    -
  • - ` +
  • + +
    + ${ + movie.poster_path + ? /*html */ ` + ${movie.title} + ` + : /*html */ ` +
    + No Image +
    + ` + } +

    ${movie.title}

    +

    + 별점 ${movie.vote_average} + ${movie.vote_average} +

    +
    +
    +
  • + ` ); }; diff --git a/src/components/MovieList/index.ts b/src/components/MovieList/index.ts index d8350a0e1..cad796922 100644 --- a/src/components/MovieList/index.ts +++ b/src/components/MovieList/index.ts @@ -1,7 +1,8 @@ import "./movieList.css"; +import { Movie, MovieResponse } from "../../types"; + import { Movies } from "../../domain/Movies"; -import { MovieResponse } from "../../types"; import { fetchPopularMovies, fetchSearchMovies } from "../../utils/api"; import { $ } from "../../utils/selector"; import { @@ -144,4 +145,8 @@ export class MovieList { activateScrollFetch() { $(".btn").removeAttribute("hidden"); } + + getMovieInfo(id: number, handleClickMovieCard: (movieInfo: Movie) => void) { + handleClickMovieCard(this.#movies.getMovieInfoById(id)); + } } diff --git a/src/domain/Movies.ts b/src/domain/Movies.ts index 2164d0c4c..9ab92c9f2 100644 --- a/src/domain/Movies.ts +++ b/src/domain/Movies.ts @@ -43,4 +43,13 @@ export class Movies { }; }); } + + getMovieInfoById(id: number) { + const movie = this.#list.find((item) => item.id === id); + + if (movie === undefined) + throw new Error(`${id}를 가진 영화를 찾을 수 없습니다.`); + + return movie; + } } From e6333f69d52a83829420296741983cf17a991cdd Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Thu, 23 Mar 2023 13:54:19 +0900 Subject: [PATCH 05/38] =?UTF-8?q?feat:=20=EB=AA=A8=EB=8B=AC=20=EC=97=AC?= =?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 --- index.html | 4 + src/App.ts | 22 +++-- src/components/Modal/index.css | 87 ++++++++++++++++++ src/components/Modal/index.ts | 91 +++++++++++++++++++ .../MovieList/{movieList.css => index.css} | 0 src/components/MovieList/index.ts | 2 +- src/types.d.ts | 60 ++++++++++++ src/utils/api.ts | 23 +++++ 8 files changed, 281 insertions(+), 8 deletions(-) create mode 100644 src/components/Modal/index.css create mode 100644 src/components/Modal/index.ts rename src/components/MovieList/{movieList.css => index.css} (100%) diff --git a/index.html b/index.html index b6c486869..0383a9f4d 100644 --- a/index.html +++ b/index.html @@ -7,6 +7,10 @@ 영화 리뷰 +
    diff --git a/src/App.ts b/src/App.ts index 6c6899951..b5ccc458f 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,4 +1,5 @@ import { Header } from "./components/Header"; +import { Modal } from "./components/Modal"; import { MovieList } from "./components/MovieList"; import { Movie } from "./types"; import { $ } from "./utils/selector"; @@ -6,10 +7,12 @@ import { $ } from "./utils/selector"; export class App { #header; #movieList; + #modal; constructor() { const $header = $("header"); const $movieList = $(".item-list"); + const $modal = $(".modal"); this.#header = new Header( $header, @@ -19,17 +22,22 @@ export class App { this.#movieList = new MovieList($movieList); + this.#modal = new Modal($modal); + this.bindEvent(); } bindEvent() { window.addEventListener("popstate", () => { - const selectedMovieId = Number(window.location.hash.replace("#", "")); + const hashString = window.location.hash.replace("#", ""); + const selectedMovieId = Number(hashString); + + if (hashString !== "") { + this.onClickMovieCard(selectedMovieId); + return; + } - this.#movieList.getMovieInfo( - selectedMovieId, - this.onClickMovieCard.bind(this) - ); + this.#modal.close(); }); } @@ -51,7 +59,7 @@ export class App { this.#movieList.changeShowTarget("popular"); } - onClickMovieCard(movie: Movie) { - console.log(movie); + onClickMovieCard(movieId: number) { + this.#modal.open(movieId); } } diff --git a/src/components/Modal/index.css b/src/components/Modal/index.css new file mode 100644 index 000000000..e80f0e256 --- /dev/null +++ b/src/components/Modal/index.css @@ -0,0 +1,87 @@ +.modal-section { + position: fixed; + top: 0; + + width: 100vw; + height: 100vh; + + display: flex; + justify-content: center; + align-items: center; +} + +.modal-backdrop { + position: fixed; + top: 0; + + width: 100vw; + height: 100vh; + + background-color: rgba(0, 0, 0, 0.3); +} + +.modal { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + width: 826px; + height: 577px; + + background-color: #222; + + z-index: 999; +} + +.modal-header { + display: flex; + justify-content: center; + align-items: center; + + height: 60px; + + border-bottom: 1px solid #f1f1f1; +} + +.modal-content { + display: flex; + justify-content: center; + align-items: center; + gap: 32px; + + height: calc(100% - 60px); +} + +.modal-image-container { + width: 292px; + height: 433px; +} + +.modal-image { + width: 100%; + height: 100%; +} + +.skeleton { + background: linear-gradient(-90deg, #aaa, #f0f0f0, #aaa, #f0f0f0); + background-size: 400%; + animation: skeleton-animation 5s infinite ease-out; + border-radius: 8px; +} + +.modal-movie-detail { + width: 438px; + height: 433px; +} + +.modal-movie-genre { + display: flex; + align-items: center; + gap: 10px; +} + +.modal-movie-genre > span { + display: flex; + align-items: center; +} diff --git a/src/components/Modal/index.ts b/src/components/Modal/index.ts new file mode 100644 index 000000000..73319314a --- /dev/null +++ b/src/components/Modal/index.ts @@ -0,0 +1,91 @@ +import "./index.css"; + +import { MovieDetail } from "../../types"; +import { fetchMovieDetailById } from "../../utils/api"; +import starImg from "../../../templates/star_filled.png"; +import { $ } from "../../utils/selector"; + +export class Modal { + #$target; + + constructor($target: Element) { + this.#$target = $target; + + this.bindEvent(); + } + + open(movieId: number) { + const modalSection = $(".modal-section"); + + fetchMovieDetailById(movieId).then((movieDetail) => { + this.render(movieDetail); + + if (modalSection instanceof HTMLElement) + modalSection.style.display = "block"; + }); + } + + close() { + const modalSection = $(".modal-section"); + + if (modalSection instanceof HTMLElement) + modalSection.style.display = "none"; + + this.reset(); + } + + reset() { + const modalImage = $(".modal-image"); + const modalDesc = $(".modal-movie-description"); + const topPosition = window.scrollY; + + if (modalImage instanceof HTMLImageElement) modalImage.src = ""; + + if (modalDesc instanceof HTMLParagraphElement) modalDesc.innerHTML = ""; + + window.location.hash = ""; + window.scrollTo(0, topPosition); + } + + bindEvent() { + $(".modal-backdrop").addEventListener("click", () => { + this.close(); + }); + } + + render(movie: MovieDetail) { + this.#$target.innerHTML = this.getMovieDetailTemplate(movie); + } + + getMovieDetailTemplate(movie: MovieDetail) { + return /*html */ ` + + + `; + } +} diff --git a/src/components/MovieList/movieList.css b/src/components/MovieList/index.css similarity index 100% rename from src/components/MovieList/movieList.css rename to src/components/MovieList/index.css diff --git a/src/components/MovieList/index.ts b/src/components/MovieList/index.ts index cad796922..104f3aecf 100644 --- a/src/components/MovieList/index.ts +++ b/src/components/MovieList/index.ts @@ -1,4 +1,4 @@ -import "./movieList.css"; +import "./index.css"; import { Movie, MovieResponse } from "../../types"; diff --git a/src/types.d.ts b/src/types.d.ts index 8c13f9fb8..4eae2990a 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -15,9 +15,69 @@ export interface MovieResponse { vote_count: number; } +export interface MovieDetailResponse { + adult: boolean; + backdrop_path: string; + belongs_to_collection: any; + budget: number; + genres: Genre[]; + homepage: string; + id: number; + imdb_id: string; + original_language: string; + original_title: string; + overview: string; + popularity: number; + poster_path: string; + production_companies: ProductionCompany[]; + production_countries: ProductionCountry[]; + release_date: string; + revenue: number; + runtime: number; + spoken_languages: SpokenLanguage[]; + status: string; + tagline: string; + title: string; + video: boolean; + vote_average: number; + vote_count: number; +} + +export interface Genre { + id: number; + name: string; +} + +interface ProductionCompany { + id: number; + logo_path?: string; + name: string; + origin_country: string; +} + +interface ProductionCountry { + iso_3166_1: string; + name: string; +} + +interface SpokenLanguage { + english_name: string; + iso_639_1: string; + name: string; +} + export interface Movie { id: number; poster_path: string; title: string; vote_average: number; } + +export interface MovieDetail { + id: number; + poster_path: string; + title: string; + vote_average: number; + genre: string[]; + overview: string; +} diff --git a/src/utils/api.ts b/src/utils/api.ts index fecfe105a..ed92a0fd1 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -1,3 +1,5 @@ +import { MovieDetail, MovieDetailResponse } from "../types"; + const API_END_POINT = "https://api.themoviedb.org/3"; const request = async (url: string) => { @@ -12,6 +14,19 @@ const request = async (url: string) => { } }; +export const convertMovieDetail = ( + movieDetail: MovieDetailResponse +): MovieDetail => { + return { + id: movieDetail.id, + poster_path: movieDetail.poster_path, + title: movieDetail.title, + vote_average: movieDetail.vote_average, + genre: movieDetail.genres.map((data) => data.name), + overview: movieDetail.overview, + }; +}; + export const fetchPopularMovies = (page: number) => { const url = `${API_END_POINT}/movie/popular?api_key=${process.env.API_KEY}&language=ko&page=${page}`; @@ -25,3 +40,11 @@ export const fetchSearchMovies = (page: number, keyword: string) => { return request(url); }; + +export const fetchMovieDetailById = async (movieId: number) => { + const url = `${API_END_POINT}/movie/${movieId}?api_key=${process.env.API_KEY}&language=ko`; + const response = await request(url); + const movieDetail = convertMovieDetail(response); + + return movieDetail; +}; From 2045f5289fd07843c20bf188677d8a3d13ac81aa Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Thu, 23 Mar 2023 15:54:23 +0900 Subject: [PATCH 06/38] =?UTF-8?q?feat:=20=EB=AA=A8=EB=8B=AC=20=EB=8B=AB?= =?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 --- index.html | 8 +++++- src/App.ts | 2 +- src/components/Modal/index.css | 41 ++++++++++++++++++++++++++++++ src/components/Modal/index.ts | 45 ++++++++++++++++++++++----------- templates/xButton.png | Bin 0 -> 1032 bytes 5 files changed, 79 insertions(+), 17 deletions(-) create mode 100644 templates/xButton.png diff --git a/index.html b/index.html index 0383a9f4d..ff742e2b4 100644 --- a/index.html +++ b/index.html @@ -9,7 +9,13 @@
    diff --git a/src/App.ts b/src/App.ts index b5ccc458f..f88781117 100644 --- a/src/App.ts +++ b/src/App.ts @@ -12,7 +12,7 @@ export class App { constructor() { const $header = $("header"); const $movieList = $(".item-list"); - const $modal = $(".modal"); + const $modal = $(".modal-content"); this.#header = new Header( $header, diff --git a/src/components/Modal/index.css b/src/components/Modal/index.css index e80f0e256..e86ebfd26 100644 --- a/src/components/Modal/index.css +++ b/src/components/Modal/index.css @@ -38,12 +38,29 @@ display: flex; justify-content: center; align-items: center; + position: relative; height: 60px; border-bottom: 1px solid #f1f1f1; } +.modal-header--text { + font-size: 20px; + font-weight: bold; +} + +.x-button { + width: 36px; + height: 36px; + + position: absolute; + top: 12px; + right: 22px; + + cursor: pointer; +} + .modal-content { display: flex; justify-content: center; @@ -73,15 +90,39 @@ .modal-movie-detail { width: 438px; height: 433px; + + position: relative; } .modal-movie-genre { display: flex; align-items: center; gap: 10px; + + margin-bottom: 10px; } .modal-movie-genre > span { display: flex; align-items: center; } + +.modal-detail--text { + font-size: 16px; +} + +.modal-star-rate { + width: 438px; + height: 64px; + padding: 0 16px; + position: absolute; + bottom: 0; + + display: flex; + align-items: center; + gap: 10px; + + border-radius: 16px; + background-color: #383839; + font-weight: bold; +} diff --git a/src/components/Modal/index.ts b/src/components/Modal/index.ts index 73319314a..1e808a6d1 100644 --- a/src/components/Modal/index.ts +++ b/src/components/Modal/index.ts @@ -3,6 +3,7 @@ import "./index.css"; import { MovieDetail } from "../../types"; import { fetchMovieDetailById } from "../../utils/api"; import starImg from "../../../templates/star_filled.png"; +import xButton from "../../../templates/xButton.png"; import { $ } from "../../utils/selector"; export class Modal { @@ -11,6 +12,8 @@ export class Modal { constructor($target: Element) { this.#$target = $target; + ($(".x-button") as HTMLImageElement).src = xButton; + this.bindEvent(); } @@ -27,10 +30,13 @@ export class Modal { close() { const modalSection = $(".modal-section"); + const modalHeader = $(".modal-header--text"); if (modalSection instanceof HTMLElement) modalSection.style.display = "none"; + if (modalHeader instanceof HTMLElement) modalHeader.textContent = ""; + this.reset(); } @@ -51,41 +57,50 @@ export class Modal { $(".modal-backdrop").addEventListener("click", () => { this.close(); }); + + $(".x-button").addEventListener("click", () => { + this.close(); + }); + + window.addEventListener("keydown", (event: KeyboardEvent) => { + if (event.key === "Escape") this.close(); + }); } render(movie: MovieDetail) { + const header = $(".modal-header--text"); + + if (header instanceof HTMLElement) header.textContent = movie.title; + this.#$target.innerHTML = this.getMovieDetailTemplate(movie); } getMovieDetailTemplate(movie: MovieDetail) { return /*html */ ` - - `; } } diff --git a/templates/xButton.png b/templates/xButton.png new file mode 100644 index 0000000000000000000000000000000000000000..596327cc53ba8a3a94acd0846cd83184b318886f GIT binary patch literal 1032 zcmV+j1o!)iP)(AFB3vb36%q?ExjNF1GH6gFIJ9ZC; z>)>V#-1H8|N0J0urBc!GHjgsPg+f7d@_i;Mh(t#5@|;M310dNV{(u}3n-`L=CxAra zDE0-10cZ{y>M&D}*tLY>@wof!>@0<5NJ0yN-Qk@i%DRL=BOWtT$w)HjyE4D;R5DLx_B)O=LL8B~DNInise*nGx1Jnr;Ey zbSjN38e%E9HT+csCsa*(fqK(C9uK5aDd_C%gjUMZ+Eor9(?6fjPYk>3%}S?J5Q#)k z-Us=79%_-9n)(2JeL--!T(G|W1L}jh_xAP{WFyRBQeXz`?Cek^LT_&`bai#%LhDy# zNF%x23@3GPZ~(KjpWx)=MC9`Uuh+XE_w@9PVi9~IGNW8B!`4;_hKC>XUr*TB*tjH< z&1N}aA~P}ZnqNhMQTI#s`~7cm92A{I3(#LI7K3m&1jon6P%4%9yYcaH)ZinokwhZt zhztP>@xF}y^9BU`#|knXPKHn@1pWOHzVP5+3`Rzt@%PE(E7;xL1?OOHG?{l2Ki&Zj zL8dZEk+$ecn4bP<>sung!V8kwTs4Bl#jlrqXlZE~R#sM^Q4kjingBVksTo2nHo&8+ z8A#N2LIW^!4S}>meq=rqOoms$TVPbvZ*U0f!rR;1+;7Yfw-RjtnvBT^n=mHah?xtT ziI2kj{QR7U`s(T$=BQ!*uDjdA?;YYhCB}egVkC`Zv+sH7FcTjoOA|zj$^>QeV9-8s z)<)$s<{{rcYDlPWP9$FPT2?Cy6T!p7vTfq52XvnoQniItMJABHkQOqTcQvyxIr-e& z9GBhP+yoJzLl=n)Z_u1HXQuD8S{5md(H6x2J$qew6W^dV!y&A_mN z{Z`tx+LK^Mhw>07=Ah+dAcw9obltg;$Z^bQb Date: Thu, 23 Mar 2023 17:18:45 +0900 Subject: [PATCH 07/38] =?UTF-8?q?feat:=20=EB=AA=A8=EB=8B=AC=20=EB=B0=98?= =?UTF-8?q?=EC=9D=91=ED=98=95=20=EB=94=94=EC=9E=90=EC=9D=B8=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 --- src/components/Modal/index.css | 61 ++++++++++++++++++++++++++---- src/components/Modal/index.ts | 2 +- src/components/MovieList/index.css | 2 +- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/src/components/Modal/index.css b/src/components/Modal/index.css index e86ebfd26..949e9d3bb 100644 --- a/src/components/Modal/index.css +++ b/src/components/Modal/index.css @@ -22,15 +22,14 @@ .modal { position: fixed; - top: 50%; + bottom: 50%; left: 50%; - transform: translate(-50%, -50%); + transform: translate(-50%, 50%); width: 826px; height: 577px; background-color: #222; - z-index: 999; } @@ -67,7 +66,7 @@ align-items: center; gap: 32px; - height: calc(100% - 60px); + padding-top: 32px; } .modal-image-container { @@ -87,11 +86,13 @@ border-radius: 8px; } -.modal-movie-detail { +.modal-detail-container { width: 438px; height: 433px; - position: relative; + display: flex; + flex-direction: column; + justify-content: space-between; } .modal-movie-genre { @@ -115,8 +116,6 @@ width: 438px; height: 64px; padding: 0 16px; - position: absolute; - bottom: 0; display: flex; align-items: center; @@ -126,3 +125,49 @@ background-color: #383839; font-weight: bold; } + +@media screen and (max-width: 1220px) { + .modal { + width: 740px; + height: 544px; + } + + .modal-image-container { + width: 260px; + height: 400px; + } + + .modal-detail-container { + width: 385px; + height: 400px; + } + + .modal-star-rate { + width: 385px; + } +} + +@media screen and (max-width: 674px) { + .modal { + position: fixed; + left: 0; + bottom: 0; + transform: translate(0, 0); + + width: 100vw; + height: 485px; + } + + .modal-image-container { + display: none; + } + + .modal-detail-container { + width: 80vw; + height: 344px; + } + + .modal-star-rate { + width: 80vw; + } +} diff --git a/src/components/Modal/index.ts b/src/components/Modal/index.ts index 1e808a6d1..a3ab9199d 100644 --- a/src/components/Modal/index.ts +++ b/src/components/Modal/index.ts @@ -98,8 +98,8 @@ export class Modal { -
    +
    `; } diff --git a/src/components/MovieList/index.css b/src/components/MovieList/index.css index 4f9bf459f..d76f09398 100644 --- a/src/components/MovieList/index.css +++ b/src/components/MovieList/index.css @@ -97,7 +97,7 @@ button.primary { } } -@media screen and (max-width: 1200px) { +@media screen and (max-width: 1220px) { .item-view h2 { font-size: 28px; } From 0ab90a52ecf1590637e5c7631cc34321a2b4d1b5 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Thu, 23 Mar 2023 19:04:35 +0900 Subject: [PATCH 08/38] =?UTF-8?q?feat:=20=EB=B3=84=EC=A0=90=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=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/components/Modal/index.css | 9 ++++ src/components/Modal/index.ts | 76 ++++++++++++++++++++++++++++++---- src/utils/storage.ts | 28 +++++++++++++ 3 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 src/utils/storage.ts diff --git a/src/components/Modal/index.css b/src/components/Modal/index.css index 949e9d3bb..b79c5f480 100644 --- a/src/components/Modal/index.css +++ b/src/components/Modal/index.css @@ -126,6 +126,10 @@ font-weight: bold; } +.star-rate-select { + cursor: pointer; +} + @media screen and (max-width: 1220px) { .modal { width: 740px; @@ -169,5 +173,10 @@ .modal-star-rate { width: 80vw; + justify-content: center; + } + + .star-rate-desc { + display: none; } } diff --git a/src/components/Modal/index.ts b/src/components/Modal/index.ts index a3ab9199d..86bf686e5 100644 --- a/src/components/Modal/index.ts +++ b/src/components/Modal/index.ts @@ -2,9 +2,11 @@ import "./index.css"; import { MovieDetail } from "../../types"; import { fetchMovieDetailById } from "../../utils/api"; -import starImg from "../../../templates/star_filled.png"; +import filledStarImg from "../../../templates/star_filled.png"; +import emptyStarImg from "../../../templates/star_empty.png"; import xButton from "../../../templates/xButton.png"; import { $ } from "../../utils/selector"; +import { getStarRateById, setStarRateById } from "../../utils/storage"; export class Modal { #$target; @@ -21,7 +23,7 @@ export class Modal { const modalSection = $(".modal-section"); fetchMovieDetailById(movieId).then((movieDetail) => { - this.render(movieDetail); + this.render(movieDetail, movieId); if (modalSection instanceof HTMLElement) modalSection.style.display = "block"; @@ -65,17 +67,41 @@ export class Modal { window.addEventListener("keydown", (event: KeyboardEvent) => { if (event.key === "Escape") this.close(); }); + + $(".modal-content").addEventListener("click", (event: Event) => { + if (!(event.target instanceof HTMLImageElement)) return; + + const movieId = Number(event.target.dataset.movieId); + const starRate = Number(event.target.dataset.starRate) + 1; + + this.renderStars(movieId, starRate); + + setStarRateById(movieId, starRate); + }); } - render(movie: MovieDetail) { + render(movie: MovieDetail, movieId: number) { const header = $(".modal-header--text"); if (header instanceof HTMLElement) header.textContent = movie.title; - this.#$target.innerHTML = this.getMovieDetailTemplate(movie); + this.#$target.innerHTML = this.getMovieDetailTemplate( + movie, + getStarRateById(movieId) + ); } - getMovieDetailTemplate(movie: MovieDetail) { + renderStars(movieId: number, starRate: number) { + const starRateContainer = $(".modal-star-rate"); + + if (starRateContainer instanceof HTMLElement) + starRateContainer.innerHTML = this.getStarSelectContainerTemplate( + movieId, + starRate + ); + } + + getMovieDetailTemplate(movie: MovieDetail, starRate: number) { return /*html */ ` - + `; } + + getStarSelectContainerTemplate(movieId: number, starRate: number) { + const imgArray = new Array(5).fill("").map( + (_, index) => + `별점` + ); + + return /*html*/ ` + 내 별점 + + ${imgArray.join("")} + + ${starRate} + ${STAR_RATE_STRING[starRate]} + `; + } } + +const STAR_RATE_STRING = [ + "나의 점수는?", + "정말 별로예요", + "별로예요", + "보통이예요", + "재밌어요", + "정말 재밌어요", +]; diff --git a/src/utils/storage.ts b/src/utils/storage.ts new file mode 100644 index 000000000..36d2a7556 --- /dev/null +++ b/src/utils/storage.ts @@ -0,0 +1,28 @@ +export const getStarRateById = (movieId: number) => { + const storageData = localStorage.getItem("starRate"); + + if (storageData) { + const starRate = JSON.parse(storageData)[`${movieId}`]; + + if (starRate) return Number(starRate); + } + + return 0; +}; + +export const setStarRateById = (movieId: number, starRate: number) => { + const storageData = localStorage.getItem("starRate"); + + if (storageData) { + const json = JSON.parse(storageData); + + localStorage.setItem( + "starRate", + JSON.stringify({ ...json, [movieId]: starRate }) + ); + + return; + } + + localStorage.setItem("starRate", JSON.stringify({ [movieId]: starRate })); +}; From dc4a7c60776230651d17784e4bdaa99e83e44f18 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Thu, 23 Mar 2023 19:13:53 +0900 Subject: [PATCH 09/38] =?UTF-8?q?style:=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20import=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/App.ts b/src/App.ts index f88781117..38d1a79fc 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,7 +1,6 @@ import { Header } from "./components/Header"; import { Modal } from "./components/Modal"; import { MovieList } from "./components/MovieList"; -import { Movie } from "./types"; import { $ } from "./utils/selector"; export class App { From 8c453b71a89a3f61eb50a1cc1c575b1bb9fcc0d9 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 24 Mar 2023 09:21:45 +0900 Subject: [PATCH 10/38] =?UTF-8?q?style:=20=EB=A1=9C=EC=BB=AC=20=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=20=EC=A0=9C=EC=96=B4=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/storage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 36d2a7556..b611347e4 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -1,4 +1,4 @@ -export const getStarRateById = (movieId: number) => { +export const getStarRateFromStorage = (movieId: number) => { const storageData = localStorage.getItem("starRate"); if (storageData) { @@ -10,7 +10,7 @@ export const getStarRateById = (movieId: number) => { return 0; }; -export const setStarRateById = (movieId: number, starRate: number) => { +export const setStarRateToStorage = (movieId: number, starRate: number) => { const storageData = localStorage.getItem("starRate"); if (storageData) { From 8208eed47707c5cf67aa0ae9dd4d37535c7d29be Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 24 Mar 2023 09:22:33 +0900 Subject: [PATCH 11/38] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/MovieList/index.ts | 6 +----- src/domain/Movies.ts | 9 --------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/components/MovieList/index.ts b/src/components/MovieList/index.ts index 104f3aecf..a8a723009 100644 --- a/src/components/MovieList/index.ts +++ b/src/components/MovieList/index.ts @@ -1,6 +1,6 @@ import "./index.css"; -import { Movie, MovieResponse } from "../../types"; +import { MovieResponse } from "../../types"; import { Movies } from "../../domain/Movies"; import { fetchPopularMovies, fetchSearchMovies } from "../../utils/api"; @@ -145,8 +145,4 @@ export class MovieList { activateScrollFetch() { $(".btn").removeAttribute("hidden"); } - - getMovieInfo(id: number, handleClickMovieCard: (movieInfo: Movie) => void) { - handleClickMovieCard(this.#movies.getMovieInfoById(id)); - } } diff --git a/src/domain/Movies.ts b/src/domain/Movies.ts index 9ab92c9f2..2164d0c4c 100644 --- a/src/domain/Movies.ts +++ b/src/domain/Movies.ts @@ -43,13 +43,4 @@ export class Movies { }; }); } - - getMovieInfoById(id: number) { - const movie = this.#list.find((item) => item.id === id); - - if (movie === undefined) - throw new Error(`${id}를 가진 영화를 찾을 수 없습니다.`); - - return movie; - } } From c2c6fe4c2d7c4db69f14e4c12ed042937ba46a86 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 24 Mar 2023 09:23:14 +0900 Subject: [PATCH 12/38] =?UTF-8?q?refactor:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=84=B1=20=EA=B3=A0=EB=A0=A4=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EB=AA=A8=EB=8B=AC=20=EB=82=B4=20=EB=B3=84=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=81=AC=EA=B8=B0=20=ED=99=95?= =?UTF-8?q?=EB=8C=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Modal/index.css | 10 +++++++- src/components/Modal/index.ts | 42 ++++++++++++++++++++-------------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/components/Modal/index.css b/src/components/Modal/index.css index b79c5f480..4ac92edc3 100644 --- a/src/components/Modal/index.css +++ b/src/components/Modal/index.css @@ -126,10 +126,18 @@ font-weight: bold; } -.star-rate-select { +.star-rate-select-img { + width: 32px; + height: 32px; + cursor: pointer; } +.star-select-container { + display: flex; + gap: 5px; +} + @media screen and (max-width: 1220px) { .modal { width: 740px; diff --git a/src/components/Modal/index.ts b/src/components/Modal/index.ts index 86bf686e5..e05a5f320 100644 --- a/src/components/Modal/index.ts +++ b/src/components/Modal/index.ts @@ -6,7 +6,10 @@ import filledStarImg from "../../../templates/star_filled.png"; import emptyStarImg from "../../../templates/star_empty.png"; import xButton from "../../../templates/xButton.png"; import { $ } from "../../utils/selector"; -import { getStarRateById, setStarRateById } from "../../utils/storage"; +import { + getStarRateFromStorage, + setStarRateToStorage, +} from "../../utils/storage"; export class Modal { #$target; @@ -76,7 +79,7 @@ export class Modal { this.renderStars(movieId, starRate); - setStarRateById(movieId, starRate); + setStarRateToStorage(movieId, starRate); }); } @@ -87,7 +90,7 @@ export class Modal { this.#$target.innerHTML = this.getMovieDetailTemplate( movie, - getStarRateById(movieId) + getStarRateFromStorage(movieId) ); } @@ -136,25 +139,30 @@ export class Modal { } getStarSelectContainerTemplate(movieId: number, starRate: number) { - const imgArray = new Array(5).fill("").map( - (_, index) => + const imgArray = this.getStarTemplate(movieId, starRate); + + return /*html*/ ` + 내 별점 + + ${imgArray.join("")} + + ${starRate} + ${STAR_RATE_STRING[starRate]} + `; + } + + getStarTemplate(movieId: number, starRate: number) { + return Array.from( + { length: 5 }, + (_, i) => `별점` ); - - return /*html*/ ` - 내 별점 - - ${imgArray.join("")} - - ${starRate} - ${STAR_RATE_STRING[starRate]} - `; } } From 5f8fc8ec9187450f2f6504735495c04a2821536d Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 24 Mar 2023 09:49:15 +0900 Subject: [PATCH 13/38] =?UTF-8?q?fix:=20=EB=B3=84=EC=A0=90=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=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/components/Modal/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Modal/index.ts b/src/components/Modal/index.ts index e05a5f320..7583700ed 100644 --- a/src/components/Modal/index.ts +++ b/src/components/Modal/index.ts @@ -73,6 +73,7 @@ export class Modal { $(".modal-content").addEventListener("click", (event: Event) => { if (!(event.target instanceof HTMLImageElement)) return; + if (event.target.className !== "star-rate-select-img") return; const movieId = Number(event.target.dataset.movieId); const starRate = Number(event.target.dataset.starRate) + 1; From c10c137bb7ebde5bdf9879cfae794eddfaa76cbc Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 24 Mar 2023 09:49:38 +0900 Subject: [PATCH 14/38] =?UTF-8?q?style:=20=EB=AA=A8=EB=8B=AC=20=EB=82=B4?= =?UTF-8?q?=EB=B6=80=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Modal/index.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/Modal/index.css b/src/components/Modal/index.css index 4ac92edc3..ea8fe2aeb 100644 --- a/src/components/Modal/index.css +++ b/src/components/Modal/index.css @@ -110,6 +110,7 @@ .modal-detail--text { font-size: 16px; + line-height: 1.4; } .modal-star-rate { @@ -119,7 +120,7 @@ display: flex; align-items: center; - gap: 10px; + gap: 7px; border-radius: 16px; background-color: #383839; @@ -157,6 +158,10 @@ .modal-star-rate { width: 385px; } + + .modal-detail--text { + font-size: 14px; + } } @media screen and (max-width: 674px) { From d695e73b5a30aa4f28ce43ec3cc9b6af8c3309b4 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 24 Mar 2023 09:50:37 +0900 Subject: [PATCH 15/38] =?UTF-8?q?docs:=20=EB=A6=AC=EB=93=9C=EB=AF=B8?= =?UTF-8?q?=EC=97=90=20=EB=B0=B0=ED=8F=AC=20=EB=A7=81=ED=81=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 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa2286f8e..5f57ce77d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # javascript-movie-review -FE 5기 레벨1 영화관 미션 +## [배포 링크](https://kyw0716.github.io/javascript-movie-review/) From 6299cf96010fe11a725947144ba49ab78abd37ba Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 24 Mar 2023 12:03:25 +0900 Subject: [PATCH 16/38] =?UTF-8?q?style:=20=EC=98=81=ED=99=94=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20hover=20=EC=8A=A4=ED=83=80=EC=9D=BC=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 --- src/components/Modal/index.ts | 52 +++++++++++++++--------------- src/components/MovieList/index.css | 4 +++ 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/components/Modal/index.ts b/src/components/Modal/index.ts index 7583700ed..c5856882c 100644 --- a/src/components/Modal/index.ts +++ b/src/components/Modal/index.ts @@ -22,6 +22,32 @@ export class Modal { this.bindEvent(); } + bindEvent() { + $(".modal-backdrop").addEventListener("click", () => { + this.close(); + }); + + $(".x-button").addEventListener("click", () => { + this.close(); + }); + + window.addEventListener("keydown", (event: KeyboardEvent) => { + if (event.key === "Escape") this.close(); + }); + + $(".modal-content").addEventListener("click", (event: Event) => { + if (!(event.target instanceof HTMLImageElement)) return; + if (event.target.className !== "star-rate-select-img") return; + + const movieId = Number(event.target.dataset.movieId); + const starRate = Number(event.target.dataset.starRate) + 1; + + this.renderStars(movieId, starRate); + + setStarRateToStorage(movieId, starRate); + }); + } + open(movieId: number) { const modalSection = $(".modal-section"); @@ -58,32 +84,6 @@ export class Modal { window.scrollTo(0, topPosition); } - bindEvent() { - $(".modal-backdrop").addEventListener("click", () => { - this.close(); - }); - - $(".x-button").addEventListener("click", () => { - this.close(); - }); - - window.addEventListener("keydown", (event: KeyboardEvent) => { - if (event.key === "Escape") this.close(); - }); - - $(".modal-content").addEventListener("click", (event: Event) => { - if (!(event.target instanceof HTMLImageElement)) return; - if (event.target.className !== "star-rate-select-img") return; - - const movieId = Number(event.target.dataset.movieId); - const starRate = Number(event.target.dataset.starRate) + 1; - - this.renderStars(movieId, starRate); - - setStarRateToStorage(movieId, starRate); - }); - } - render(movie: MovieDetail, movieId: number) { const header = $(".modal-header--text"); diff --git a/src/components/MovieList/index.css b/src/components/MovieList/index.css index d76f09398..1c0aa49e4 100644 --- a/src/components/MovieList/index.css +++ b/src/components/MovieList/index.css @@ -32,6 +32,10 @@ flex-direction: column; } +.item-card:hover { + transform: scale(1.1); +} + .item-thumbnail { border-radius: 8px; width: 180px; From 1b04be9b7fe3c9218dea61bfe7f5cb935a43afff Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 24 Mar 2023 14:20:40 +0900 Subject: [PATCH 17/38] =?UTF-8?q?refactor:=20=EC=A0=95=EB=B3=B4=EA=B0=80?= =?UTF-8?q?=20=EC=97=86=EB=8A=94=20=EA=B2=BD=EC=9A=B0=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20UI=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Modal/index.ts | 62 ++++++++++++++++++++---------- src/components/MovieList/index.css | 4 -- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/components/Modal/index.ts b/src/components/Modal/index.ts index c5856882c..8ce6e511e 100644 --- a/src/components/Modal/index.ts +++ b/src/components/Modal/index.ts @@ -108,32 +108,54 @@ export class Modal { getMovieDetailTemplate(movie: MovieDetail, starRate: number) { return /*html */ ` `; @@ -143,9 +165,9 @@ export class Modal { const imgArray = this.getStarTemplate(movieId, starRate); return /*html*/ ` - 내 별점 + 내 별점 - ${imgArray.join("")} + ${imgArray.join("")} ${starRate} ${STAR_RATE_STRING[starRate]} @@ -159,9 +181,9 @@ export class Modal { `별점` ); } diff --git a/src/components/MovieList/index.css b/src/components/MovieList/index.css index 1c0aa49e4..d76f09398 100644 --- a/src/components/MovieList/index.css +++ b/src/components/MovieList/index.css @@ -32,10 +32,6 @@ flex-direction: column; } -.item-card:hover { - transform: scale(1.1); -} - .item-thumbnail { border-radius: 8px; width: 180px; From 8ad3bb1666c0064c2c52a5b037bd603352fec4d2 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 24 Mar 2023 20:00:08 +0900 Subject: [PATCH 18/38] =?UTF-8?q?test:=20=EC=8A=A4=EC=BC=88=EB=A0=88?= =?UTF-8?q?=ED=86=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cypress/e2e/spec.cy.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/spec.cy.ts b/cypress/e2e/spec.cy.ts index a4c37c14c..e0fc889b2 100644 --- a/cypress/e2e/spec.cy.ts +++ b/cypress/e2e/spec.cy.ts @@ -41,8 +41,10 @@ describe("영화 리뷰 웹 테스트", () => { }); }); - it("더보기 버튼 클릭시 다음 페이지 정보가 렌더링 된다.", () => { - cy.get(".btn").click(); + it.only("영화 목록을 마지막 요소까지 스크롤시 다음 페이지 정보가 렌더링 된다.", () => { + cy.scrollTo("bottom"); + + cy.get(".skeleton-container").should("be.visible"); cy.wait("@getPopularMoviesPage2").then((interception) => { const movieItems = interception.response?.body.results; From 0133ba0e64a65a162b1487cefbb346ef18a4f5c5 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 24 Mar 2023 20:17:17 +0900 Subject: [PATCH 19/38] =?UTF-8?q?chore:=20cypress=20support=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cypress.config.ts | 1 + cypress/e2e/spec.cy.ts | 2 +- cypress/support/commands.ts | 0 cypress/support/e2e.ts | 0 4 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 cypress/support/commands.ts delete mode 100644 cypress/support/e2e.ts diff --git a/cypress.config.ts b/cypress.config.ts index 17161e32e..3b44444d4 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -5,5 +5,6 @@ export default defineConfig({ setupNodeEvents(on, config) { // implement node event listeners here }, + supportFile: false, }, }); diff --git a/cypress/e2e/spec.cy.ts b/cypress/e2e/spec.cy.ts index e0fc889b2..903b3672b 100644 --- a/cypress/e2e/spec.cy.ts +++ b/cypress/e2e/spec.cy.ts @@ -41,7 +41,7 @@ describe("영화 리뷰 웹 테스트", () => { }); }); - it.only("영화 목록을 마지막 요소까지 스크롤시 다음 페이지 정보가 렌더링 된다.", () => { + it("영화 목록을 마지막 요소까지 스크롤시 다음 페이지 정보가 렌더링 된다.", () => { cy.scrollTo("bottom"); cy.get(".skeleton-container").should("be.visible"); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts deleted file mode 100644 index e69de29bb..000000000 From f5459d499183270f5792b12c5137275dcac02f38 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 24 Mar 2023 20:33:17 +0900 Subject: [PATCH 20/38] =?UTF-8?q?refactor:=20api=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/api.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/utils/api.ts b/src/utils/api.ts index ed92a0fd1..9d5ff8bc5 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -9,8 +9,12 @@ const request = async (url: string) => { if (response.ok) { return response.json(); } + + throw new Error(`${response.status}`); } catch (error: any) { - alert("정보를 가져오는 중 에러가 발생했습니다!"); + if (error.message === "Failed to fatch") + return alert("네트워크 연결이 종료되었습니다."); + alert(`${error.message} 에러가 발생했습니다!`); } }; From a167d75256c3a0cb31f881a4540f47133ac65aad Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Sun, 26 Mar 2023 17:51:35 +0900 Subject: [PATCH 21/38] =?UTF-8?q?refactor:=20=EC=98=81=ED=99=94=20?= =?UTF-8?q?=EB=94=94=ED=85=8C=EC=9D=BC=20=EB=AA=A8=EB=8B=AC=20=EB=9D=84?= =?UTF-8?q?=EC=9A=B0=EB=8A=94=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?popstate=20=3D>=20click?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.ts | 19 ++++++++----------- src/components/MovieList/MovieCard.ts | 4 +--- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/App.ts b/src/App.ts index 38d1a79fc..da483deeb 100644 --- a/src/App.ts +++ b/src/App.ts @@ -23,20 +23,17 @@ export class App { this.#modal = new Modal($modal); - this.bindEvent(); + this.bindEvent($movieList); } - bindEvent() { - window.addEventListener("popstate", () => { - const hashString = window.location.hash.replace("#", ""); - const selectedMovieId = Number(hashString); + bindEvent($movieList: Element) { + $movieList.addEventListener("click", (event: Event) => { + if (!(event.target instanceof HTMLElement)) return; - if (hashString !== "") { - this.onClickMovieCard(selectedMovieId); - return; - } + const movieCard = event.target.closest("li"); + const movieId = movieCard?.dataset.movieId; - this.#modal.close(); + if (movieId) this.onClickMovieCard(Number(movieId)); }); } @@ -59,6 +56,6 @@ export class App { } onClickMovieCard(movieId: number) { - this.#modal.open(movieId); + this.#modal.open(movieId, "movieDetail"); } } diff --git a/src/components/MovieList/MovieCard.ts b/src/components/MovieList/MovieCard.ts index 2475866a9..57abf7c61 100644 --- a/src/components/MovieList/MovieCard.ts +++ b/src/components/MovieList/MovieCard.ts @@ -6,8 +6,7 @@ export const getMovieCardTemplate = (movie: Movie) => { return ( /*html*/ ` -
  • - +
  • ${ movie.poster_path @@ -31,7 +30,6 @@ export const getMovieCardTemplate = (movie: Movie) => { ${movie.vote_average}

    -
  • ` ); From 2bcf9edcb76adc6a610ced157f1549764318079c Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Sun, 26 Mar 2023 17:51:51 +0900 Subject: [PATCH 22/38] =?UTF-8?q?refactor:=20=EB=AA=A8=EB=8B=AC=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MovieDetailModal/StarSelectContainer.ts | 51 ++++++ .../Modal/MovieDetailModal/index.css | 124 +++++++++++++ .../Modal/MovieDetailModal/index.ts | 108 ++++++++++++ src/components/Modal/index.css | 125 ------------- src/components/Modal/index.ts | 165 ++---------------- src/components/MovieList/index.css | 2 + 6 files changed, 295 insertions(+), 280 deletions(-) create mode 100644 src/components/Modal/MovieDetailModal/StarSelectContainer.ts create mode 100644 src/components/Modal/MovieDetailModal/index.css create mode 100644 src/components/Modal/MovieDetailModal/index.ts diff --git a/src/components/Modal/MovieDetailModal/StarSelectContainer.ts b/src/components/Modal/MovieDetailModal/StarSelectContainer.ts new file mode 100644 index 000000000..16e5ae5f3 --- /dev/null +++ b/src/components/Modal/MovieDetailModal/StarSelectContainer.ts @@ -0,0 +1,51 @@ +import filledStarImg from "../../../../templates/star_filled.png"; +import emptyStarImg from "../../../../templates/star_empty.png"; +import { $ } from "../../../utils/selector"; + +export class StarSelectContainer { + renderStars(movieId: number, starRate: number) { + const starRateContainer = $(".modal-star-rate"); + + if (starRateContainer instanceof HTMLElement) + starRateContainer.innerHTML = this.getStarSelectContainerTemplate( + movieId, + starRate + ); + } + + getStarSelectContainerTemplate(movieId: number, starRate: number) { + const imgArray = this.getStarTemplate(movieId, starRate); + + return /*html*/ ` + 내 별점 + + ${imgArray.join("")} + + ${starRate * 2}점 + ${STAR_RATE_STRING[starRate]} + `; + } + + getStarTemplate(movieId: number, starRate: number) { + return Array.from( + { length: 5 }, + (_, i) => + `별점` + ); + } +} + +const STAR_RATE_STRING = [ + "나의 점수는?", + "최악이예요", + "별로예요", + "보통이예요", + "재미있어요", + "명작이예요", +]; diff --git a/src/components/Modal/MovieDetailModal/index.css b/src/components/Modal/MovieDetailModal/index.css new file mode 100644 index 000000000..3eff01329 --- /dev/null +++ b/src/components/Modal/MovieDetailModal/index.css @@ -0,0 +1,124 @@ +.modal-image-container { + width: 292px; + height: 433px; +} + +.modal-image { + width: 100%; + height: 100%; +} + +.skeleton { + background: linear-gradient(-90deg, #aaa, #f0f0f0, #aaa, #f0f0f0); + background-size: 400%; + animation: skeleton-animation 5s infinite ease-out; + border-radius: 8px; +} + +.modal-detail-container { + width: 438px; + height: 433px; + + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.modal-movie-genre { + display: flex; + align-items: center; + gap: 10px; + + margin-bottom: 10px; +} + +.modal-movie-genre > span { + display: flex; + align-items: center; +} + +.modal-detail--text { + font-size: 16px; + line-height: 1.4; +} + +.modal-star-rate { + width: 438px; + height: 64px; + padding: 0 16px; + + display: flex; + align-items: center; + gap: 7px; + + border-radius: 16px; + background-color: #383839; + font-weight: bold; +} + +.star-rate-select-img { + width: 32px; + height: 32px; + + cursor: pointer; +} + +.star-select-container { + display: flex; + gap: 5px; +} + +@media screen and (max-width: 1220px) { + .modal { + width: 740px; + height: 544px; + } + + .modal-image-container { + width: 260px; + height: 400px; + } + + .modal-detail-container { + width: 385px; + height: 400px; + } + + .modal-star-rate { + width: 385px; + } + + .modal-detail--text { + font-size: 14px; + } +} + +@media screen and (max-width: 674px) { + .modal { + position: fixed; + left: 0; + bottom: 0; + transform: translate(0, 0); + + width: 100vw; + height: 485px; + } + + .modal-image-container { + display: none; + } + + .modal-detail-container { + width: 80vw; + height: 344px; + } + + .modal-star-rate { + width: 80vw; + justify-content: center; + } + + .star-rate-desc { + display: none; + } +} diff --git a/src/components/Modal/MovieDetailModal/index.ts b/src/components/Modal/MovieDetailModal/index.ts new file mode 100644 index 000000000..1d08f2ae4 --- /dev/null +++ b/src/components/Modal/MovieDetailModal/index.ts @@ -0,0 +1,108 @@ +import "./index.css"; + +import { MovieDetail } from "../../../types"; + +import filledStarImg from "../../../../templates/star_filled.png"; + +import { + getStarRateFromStorage, + setStarRateToStorage, +} from "../../../utils/storage"; +import { $ } from "../../../utils/selector"; +import { StarSelectContainer } from "./StarSelectContainer"; + +export class MovieDetailModal { + #$target; + #$StarSelectContainer; + + constructor($target: Element) { + this.#$target = $target; + this.#$StarSelectContainer = new StarSelectContainer(); + + this.bindEvent(); + } + + bindEvent() { + $(".modal-content").addEventListener("click", (event: Event) => { + if (!(event.target instanceof HTMLImageElement)) return; + if (event.target.className !== "star-rate-select-img") return; + + const movieId = Number(event.target.dataset.movieId); + const starRate = Number(event.target.dataset.starRate) + 1; + + this.#$StarSelectContainer.renderStars(movieId, starRate); + + setStarRateToStorage(movieId, starRate); + }); + } + + render(movie: MovieDetail, movieId: number) { + const header = $(".modal-header--text"); + + if (header instanceof HTMLElement) header.textContent = movie.title; + + this.#$target.innerHTML = this.getMovieDetailTemplate( + movie, + getStarRateFromStorage(movieId) + ); + } + + getMovieDetailTemplate(movie: MovieDetail, starRate: number) { + return /*html */ ` + + + `; + } +} diff --git a/src/components/Modal/index.css b/src/components/Modal/index.css index ea8fe2aeb..6bc879137 100644 --- a/src/components/Modal/index.css +++ b/src/components/Modal/index.css @@ -68,128 +68,3 @@ padding-top: 32px; } - -.modal-image-container { - width: 292px; - height: 433px; -} - -.modal-image { - width: 100%; - height: 100%; -} - -.skeleton { - background: linear-gradient(-90deg, #aaa, #f0f0f0, #aaa, #f0f0f0); - background-size: 400%; - animation: skeleton-animation 5s infinite ease-out; - border-radius: 8px; -} - -.modal-detail-container { - width: 438px; - height: 433px; - - display: flex; - flex-direction: column; - justify-content: space-between; -} - -.modal-movie-genre { - display: flex; - align-items: center; - gap: 10px; - - margin-bottom: 10px; -} - -.modal-movie-genre > span { - display: flex; - align-items: center; -} - -.modal-detail--text { - font-size: 16px; - line-height: 1.4; -} - -.modal-star-rate { - width: 438px; - height: 64px; - padding: 0 16px; - - display: flex; - align-items: center; - gap: 7px; - - border-radius: 16px; - background-color: #383839; - font-weight: bold; -} - -.star-rate-select-img { - width: 32px; - height: 32px; - - cursor: pointer; -} - -.star-select-container { - display: flex; - gap: 5px; -} - -@media screen and (max-width: 1220px) { - .modal { - width: 740px; - height: 544px; - } - - .modal-image-container { - width: 260px; - height: 400px; - } - - .modal-detail-container { - width: 385px; - height: 400px; - } - - .modal-star-rate { - width: 385px; - } - - .modal-detail--text { - font-size: 14px; - } -} - -@media screen and (max-width: 674px) { - .modal { - position: fixed; - left: 0; - bottom: 0; - transform: translate(0, 0); - - width: 100vw; - height: 485px; - } - - .modal-image-container { - display: none; - } - - .modal-detail-container { - width: 80vw; - height: 344px; - } - - .modal-star-rate { - width: 80vw; - justify-content: center; - } - - .star-rate-desc { - display: none; - } -} diff --git a/src/components/Modal/index.ts b/src/components/Modal/index.ts index 8ce6e511e..e47db131a 100644 --- a/src/components/Modal/index.ts +++ b/src/components/Modal/index.ts @@ -1,21 +1,15 @@ import "./index.css"; -import { MovieDetail } from "../../types"; import { fetchMovieDetailById } from "../../utils/api"; -import filledStarImg from "../../../templates/star_filled.png"; -import emptyStarImg from "../../../templates/star_empty.png"; import xButton from "../../../templates/xButton.png"; import { $ } from "../../utils/selector"; -import { - getStarRateFromStorage, - setStarRateToStorage, -} from "../../utils/storage"; +import { MovieDetailModal } from "./MovieDetailModal"; export class Modal { - #$target; + #$movieDetail; constructor($target: Element) { - this.#$target = $target; + this.#$movieDetail = new MovieDetailModal($target); ($(".x-button") as HTMLImageElement).src = xButton; @@ -34,29 +28,18 @@ export class Modal { window.addEventListener("keydown", (event: KeyboardEvent) => { if (event.key === "Escape") this.close(); }); - - $(".modal-content").addEventListener("click", (event: Event) => { - if (!(event.target instanceof HTMLImageElement)) return; - if (event.target.className !== "star-rate-select-img") return; - - const movieId = Number(event.target.dataset.movieId); - const starRate = Number(event.target.dataset.starRate) + 1; - - this.renderStars(movieId, starRate); - - setStarRateToStorage(movieId, starRate); - }); } - open(movieId: number) { + open(movieId: number, modalType: "movieDetail" | string) { const modalSection = $(".modal-section"); - fetchMovieDetailById(movieId).then((movieDetail) => { - this.render(movieDetail, movieId); + if (modalType === "movieDetail") + fetchMovieDetailById(movieId).then((movieDetail) => { + this.#$movieDetail.render(movieDetail, movieId); - if (modalSection instanceof HTMLElement) - modalSection.style.display = "block"; - }); + if (modalSection instanceof HTMLElement) + modalSection.style.display = "block"; + }); } close() { @@ -67,133 +50,5 @@ export class Modal { modalSection.style.display = "none"; if (modalHeader instanceof HTMLElement) modalHeader.textContent = ""; - - this.reset(); - } - - reset() { - const modalImage = $(".modal-image"); - const modalDesc = $(".modal-movie-description"); - const topPosition = window.scrollY; - - if (modalImage instanceof HTMLImageElement) modalImage.src = ""; - - if (modalDesc instanceof HTMLParagraphElement) modalDesc.innerHTML = ""; - - window.location.hash = ""; - window.scrollTo(0, topPosition); - } - - render(movie: MovieDetail, movieId: number) { - const header = $(".modal-header--text"); - - if (header instanceof HTMLElement) header.textContent = movie.title; - - this.#$target.innerHTML = this.getMovieDetailTemplate( - movie, - getStarRateFromStorage(movieId) - ); - } - - renderStars(movieId: number, starRate: number) { - const starRateContainer = $(".modal-star-rate"); - - if (starRateContainer instanceof HTMLElement) - starRateContainer.innerHTML = this.getStarSelectContainerTemplate( - movieId, - starRate - ); - } - - getMovieDetailTemplate(movie: MovieDetail, starRate: number) { - return /*html */ ` - - - `; - } - - getStarSelectContainerTemplate(movieId: number, starRate: number) { - const imgArray = this.getStarTemplate(movieId, starRate); - - return /*html*/ ` - 내 별점 - - ${imgArray.join("")} - - ${starRate} - ${STAR_RATE_STRING[starRate]} - `; - } - - getStarTemplate(movieId: number, starRate: number) { - return Array.from( - { length: 5 }, - (_, i) => - `별점` - ); } } - -const STAR_RATE_STRING = [ - "나의 점수는?", - "정말 별로예요", - "별로예요", - "보통이예요", - "재밌어요", - "정말 재밌어요", -]; diff --git a/src/components/MovieList/index.css b/src/components/MovieList/index.css index d76f09398..553e96e3f 100644 --- a/src/components/MovieList/index.css +++ b/src/components/MovieList/index.css @@ -30,6 +30,8 @@ .item-card { display: flex; flex-direction: column; + + cursor: pointer; } .item-thumbnail { From c8e19622f8fd1eaecc2314432162efff28527b70 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Mon, 27 Mar 2023 13:45:26 +0900 Subject: [PATCH 23/38] =?UTF-8?q?feat:=20hover=EB=A1=9C=20=EB=B3=84?= =?UTF-8?q?=EC=A0=90=20=EC=84=A0=ED=83=9D=ED=95=98=EA=B8=B0=20=EA=B8=B0?= =?UTF-8?q?=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 --- .../Modal/MovieDetailModal/StarSelectContainer.ts | 14 +++++++------- src/components/Modal/MovieDetailModal/index.ts | 13 +++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/components/Modal/MovieDetailModal/StarSelectContainer.ts b/src/components/Modal/MovieDetailModal/StarSelectContainer.ts index 16e5ae5f3..08bfc3bae 100644 --- a/src/components/Modal/MovieDetailModal/StarSelectContainer.ts +++ b/src/components/Modal/MovieDetailModal/StarSelectContainer.ts @@ -17,13 +17,13 @@ export class StarSelectContainer { const imgArray = this.getStarTemplate(movieId, starRate); return /*html*/ ` - 내 별점 - - ${imgArray.join("")} - - ${starRate * 2}점 - ${STAR_RATE_STRING[starRate]} - `; + 내 별점 + + ${imgArray.join("")} + + ${starRate * 2}점 + ${STAR_RATE_STRING[starRate]} + `; } getStarTemplate(movieId: number, starRate: number) { diff --git a/src/components/Modal/MovieDetailModal/index.ts b/src/components/Modal/MovieDetailModal/index.ts index 1d08f2ae4..faa5fa9a7 100644 --- a/src/components/Modal/MovieDetailModal/index.ts +++ b/src/components/Modal/MovieDetailModal/index.ts @@ -34,6 +34,19 @@ export class MovieDetailModal { setStarRateToStorage(movieId, starRate); }); + + $(".modal-content").addEventListener("mouseover", (event: Event) => { + if (!(event.target instanceof HTMLImageElement)) return; + if (event.target.className !== "star-rate-select-img") return; + + const movieId = Number(event.target.dataset.movieId); + const starRate = Number(event.target.dataset.starRate) + 1; + + if (getStarRateFromStorage(movieId) !== starRate) { + this.#$StarSelectContainer.renderStars(movieId, starRate); + setStarRateToStorage(movieId, starRate); + } + }); } render(movie: MovieDetail, movieId: number) { From 65638a3544fc77c5b0027b2573571d88eed7dbb3 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Mon, 27 Mar 2023 14:33:31 +0900 Subject: [PATCH 24/38] =?UTF-8?q?feat:=20=EC=98=81=ED=99=94=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=EC=A0=95=EB=B3=B4=20=EB=AA=A8=EB=8B=AC=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Modal/MovieDetailModal/Description.ts | 45 ++++++++++ .../Modal/MovieDetailModal/Image.ts | 33 ++++++++ .../{StarSelectContainer.ts => StarSelect.ts} | 2 +- .../Modal/MovieDetailModal/index.ts | 84 ++++--------------- 4 files changed, 93 insertions(+), 71 deletions(-) create mode 100644 src/components/Modal/MovieDetailModal/Description.ts create mode 100644 src/components/Modal/MovieDetailModal/Image.ts rename src/components/Modal/MovieDetailModal/{StarSelectContainer.ts => StarSelect.ts} (97%) diff --git a/src/components/Modal/MovieDetailModal/Description.ts b/src/components/Modal/MovieDetailModal/Description.ts new file mode 100644 index 000000000..bc3e7856a --- /dev/null +++ b/src/components/Modal/MovieDetailModal/Description.ts @@ -0,0 +1,45 @@ +import { MovieDetail } from "../../../types"; + +import filledStarImg from "../../../../templates/star_filled.png"; + +import { StarSelect } from "./StarSelect"; + +export class Description { + #$StarSelectContainer; + + constructor() { + this.#$StarSelectContainer = new StarSelect(); + } + + getDescriptionTemplate(movie: MovieDetail, starRate: number) { + return /*html*/ ` + + `; + } +} diff --git a/src/components/Modal/MovieDetailModal/Image.ts b/src/components/Modal/MovieDetailModal/Image.ts new file mode 100644 index 000000000..395e14952 --- /dev/null +++ b/src/components/Modal/MovieDetailModal/Image.ts @@ -0,0 +1,33 @@ +export class Image { + getImageContainerTemplate(imagePath: string, title: string) { + return /*html*/ ` + + `; + } +} diff --git a/src/components/Modal/MovieDetailModal/StarSelectContainer.ts b/src/components/Modal/MovieDetailModal/StarSelect.ts similarity index 97% rename from src/components/Modal/MovieDetailModal/StarSelectContainer.ts rename to src/components/Modal/MovieDetailModal/StarSelect.ts index 08bfc3bae..6787db35b 100644 --- a/src/components/Modal/MovieDetailModal/StarSelectContainer.ts +++ b/src/components/Modal/MovieDetailModal/StarSelect.ts @@ -2,7 +2,7 @@ import filledStarImg from "../../../../templates/star_filled.png"; import emptyStarImg from "../../../../templates/star_empty.png"; import { $ } from "../../../utils/selector"; -export class StarSelectContainer { +export class StarSelect { renderStars(movieId: number, starRate: number) { const starRateContainer = $(".modal-star-rate"); diff --git a/src/components/Modal/MovieDetailModal/index.ts b/src/components/Modal/MovieDetailModal/index.ts index faa5fa9a7..8ec19c0a9 100644 --- a/src/components/Modal/MovieDetailModal/index.ts +++ b/src/components/Modal/MovieDetailModal/index.ts @@ -2,39 +2,32 @@ import "./index.css"; import { MovieDetail } from "../../../types"; -import filledStarImg from "../../../../templates/star_filled.png"; - import { getStarRateFromStorage, setStarRateToStorage, } from "../../../utils/storage"; import { $ } from "../../../utils/selector"; -import { StarSelectContainer } from "./StarSelectContainer"; +import { StarSelect } from "./StarSelect"; +import { Image } from "./Image"; +import { Description } from "./Description"; export class MovieDetailModal { #$target; + #$StarSelectContainer; + #$ImageContainer; + #$Description; constructor($target: Element) { this.#$target = $target; - this.#$StarSelectContainer = new StarSelectContainer(); + this.#$StarSelectContainer = new StarSelect(); + this.#$ImageContainer = new Image(); + this.#$Description = new Description(); this.bindEvent(); } bindEvent() { - $(".modal-content").addEventListener("click", (event: Event) => { - if (!(event.target instanceof HTMLImageElement)) return; - if (event.target.className !== "star-rate-select-img") return; - - const movieId = Number(event.target.dataset.movieId); - const starRate = Number(event.target.dataset.starRate) + 1; - - this.#$StarSelectContainer.renderStars(movieId, starRate); - - setStarRateToStorage(movieId, starRate); - }); - $(".modal-content").addEventListener("mouseover", (event: Event) => { if (!(event.target instanceof HTMLImageElement)) return; if (event.target.className !== "star-rate-select-img") return; @@ -62,60 +55,11 @@ export class MovieDetailModal { getMovieDetailTemplate(movie: MovieDetail, starRate: number) { return /*html */ ` - - + ${this.#$ImageContainer.getImageContainerTemplate( + movie.poster_path, + movie.title + )} + ${this.#$Description.getDescriptionTemplate(movie, starRate)} `; } } From c194cc6b64cedd0ba5ab6f68a08ddf55c0db7176 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Mon, 27 Mar 2023 15:03:28 +0900 Subject: [PATCH 25/38] =?UTF-8?q?style:=20import=20=EC=BB=A8=EB=B2=A4?= =?UTF-8?q?=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Header/index.ts | 1 + .../Modal/MovieDetailModal/Description.ts | 21 ++----- .../Modal/MovieDetailModal/Image.ts | 62 +++++++++---------- .../Modal/MovieDetailModal/StarSelect.ts | 60 +++++++++--------- .../Modal/MovieDetailModal/index.ts | 24 +++---- src/components/Modal/index.ts | 3 +- src/components/MovieList/index.ts | 2 +- src/utils/api.ts | 2 +- 8 files changed, 78 insertions(+), 97 deletions(-) diff --git a/src/components/Header/index.ts b/src/components/Header/index.ts index 3901a10b5..7eeed644b 100644 --- a/src/components/Header/index.ts +++ b/src/components/Header/index.ts @@ -1,6 +1,7 @@ import "./index.css"; import logoImage from "../../../templates/logo.png"; + import { $ } from "../../utils/selector"; export class Header { diff --git a/src/components/Modal/MovieDetailModal/Description.ts b/src/components/Modal/MovieDetailModal/Description.ts index bc3e7856a..d0dd38e95 100644 --- a/src/components/Modal/MovieDetailModal/Description.ts +++ b/src/components/Modal/MovieDetailModal/Description.ts @@ -1,18 +1,11 @@ -import { MovieDetail } from "../../../types"; +import type { MovieDetail } from "../../../types"; import filledStarImg from "../../../../templates/star_filled.png"; -import { StarSelect } from "./StarSelect"; +import { getStarSelectContainerTemplate } from "./StarSelect"; -export class Description { - #$StarSelectContainer; - - constructor() { - this.#$StarSelectContainer = new StarSelect(); - } - - getDescriptionTemplate(movie: MovieDetail, starRate: number) { - return /*html*/ ` +export function getDescriptionTemplate(movie: MovieDetail, starRate: number) { + return /*html*/ ` `; - } } diff --git a/src/components/Modal/MovieDetailModal/Image.ts b/src/components/Modal/MovieDetailModal/Image.ts index 395e14952..695a4f38f 100644 --- a/src/components/Modal/MovieDetailModal/Image.ts +++ b/src/components/Modal/MovieDetailModal/Image.ts @@ -1,33 +1,31 @@ -export class Image { - getImageContainerTemplate(imagePath: string, title: string) { - return /*html*/ ` - - `; - } +export function getImageContainerTemplate(imagePath: string, title: string) { + return /*html*/ ` + + `; } diff --git a/src/components/Modal/MovieDetailModal/StarSelect.ts b/src/components/Modal/MovieDetailModal/StarSelect.ts index 6787db35b..c1e7c78a3 100644 --- a/src/components/Modal/MovieDetailModal/StarSelect.ts +++ b/src/components/Modal/MovieDetailModal/StarSelect.ts @@ -1,22 +1,34 @@ import filledStarImg from "../../../../templates/star_filled.png"; import emptyStarImg from "../../../../templates/star_empty.png"; + import { $ } from "../../../utils/selector"; -export class StarSelect { - renderStars(movieId: number, starRate: number) { - const starRateContainer = $(".modal-star-rate"); +const STAR_RATE_STRING = [ + "나의 점수는?", + "최악이예요", + "별로예요", + "보통이예요", + "재미있어요", + "명작이예요", +]; + +export function renderStars(movieId: number, starRate: number) { + const starRateContainer = $(".modal-star-rate"); - if (starRateContainer instanceof HTMLElement) - starRateContainer.innerHTML = this.getStarSelectContainerTemplate( - movieId, - starRate - ); - } + if (starRateContainer instanceof HTMLElement) + starRateContainer.innerHTML = getStarSelectContainerTemplate( + movieId, + starRate + ); +} - getStarSelectContainerTemplate(movieId: number, starRate: number) { - const imgArray = this.getStarTemplate(movieId, starRate); +export function getStarSelectContainerTemplate( + movieId: number, + starRate: number +) { + const imgArray = getStarTemplate(movieId, starRate); - return /*html*/ ` + return /*html*/ ` 내 별점 ${imgArray.join("")} @@ -24,28 +36,18 @@ export class StarSelect { ${starRate * 2}점 ${STAR_RATE_STRING[starRate]} `; - } +} - getStarTemplate(movieId: number, starRate: number) { - return Array.from( - { length: 5 }, - (_, i) => - ` + `별점` - ); - } + ); } - -const STAR_RATE_STRING = [ - "나의 점수는?", - "최악이예요", - "별로예요", - "보통이예요", - "재미있어요", - "명작이예요", -]; diff --git a/src/components/Modal/MovieDetailModal/index.ts b/src/components/Modal/MovieDetailModal/index.ts index 8ec19c0a9..4595fe35c 100644 --- a/src/components/Modal/MovieDetailModal/index.ts +++ b/src/components/Modal/MovieDetailModal/index.ts @@ -1,28 +1,21 @@ import "./index.css"; -import { MovieDetail } from "../../../types"; +import type { MovieDetail } from "../../../types"; import { getStarRateFromStorage, setStarRateToStorage, } from "../../../utils/storage"; import { $ } from "../../../utils/selector"; -import { StarSelect } from "./StarSelect"; -import { Image } from "./Image"; -import { Description } from "./Description"; +import { renderStars } from "./StarSelect"; +import { getImageContainerTemplate } from "./Image"; +import { getDescriptionTemplate } from "./Description"; export class MovieDetailModal { #$target; - #$StarSelectContainer; - #$ImageContainer; - #$Description; - constructor($target: Element) { this.#$target = $target; - this.#$StarSelectContainer = new StarSelect(); - this.#$ImageContainer = new Image(); - this.#$Description = new Description(); this.bindEvent(); } @@ -36,7 +29,7 @@ export class MovieDetailModal { const starRate = Number(event.target.dataset.starRate) + 1; if (getStarRateFromStorage(movieId) !== starRate) { - this.#$StarSelectContainer.renderStars(movieId, starRate); + renderStars(movieId, starRate); setStarRateToStorage(movieId, starRate); } }); @@ -55,11 +48,8 @@ export class MovieDetailModal { getMovieDetailTemplate(movie: MovieDetail, starRate: number) { return /*html */ ` - ${this.#$ImageContainer.getImageContainerTemplate( - movie.poster_path, - movie.title - )} - ${this.#$Description.getDescriptionTemplate(movie, starRate)} + ${getImageContainerTemplate(movie.poster_path, movie.title)} + ${getDescriptionTemplate(movie, starRate)} `; } } diff --git a/src/components/Modal/index.ts b/src/components/Modal/index.ts index e47db131a..4a3b3ff4d 100644 --- a/src/components/Modal/index.ts +++ b/src/components/Modal/index.ts @@ -1,7 +1,8 @@ import "./index.css"; -import { fetchMovieDetailById } from "../../utils/api"; import xButton from "../../../templates/xButton.png"; + +import { fetchMovieDetailById } from "../../utils/api"; import { $ } from "../../utils/selector"; import { MovieDetailModal } from "./MovieDetailModal"; diff --git a/src/components/MovieList/index.ts b/src/components/MovieList/index.ts index a8a723009..f66d4464d 100644 --- a/src/components/MovieList/index.ts +++ b/src/components/MovieList/index.ts @@ -1,6 +1,6 @@ import "./index.css"; -import { MovieResponse } from "../../types"; +import type { MovieResponse } from "../../types"; import { Movies } from "../../domain/Movies"; import { fetchPopularMovies, fetchSearchMovies } from "../../utils/api"; diff --git a/src/utils/api.ts b/src/utils/api.ts index 9d5ff8bc5..30c8a2625 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -1,4 +1,4 @@ -import { MovieDetail, MovieDetailResponse } from "../types"; +import type { MovieDetail, MovieDetailResponse } from "../types"; const API_END_POINT = "https://api.themoviedb.org/3"; From bb017060d25c4a44520e8e75b2cac3e30983c069 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Mon, 27 Mar 2023 16:28:14 +0900 Subject: [PATCH 26/38] =?UTF-8?q?refactor:=20MovieDetailModal=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=9A=94=EC=B2=AD=20=EC=9C=84=EC=B9=98=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 --- src/App.ts | 2 +- .../Modal/MovieDetailModal/index.ts | 8 +++--- src/components/Modal/index.ts | 25 +++++++++++-------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/App.ts b/src/App.ts index da483deeb..f97b9743b 100644 --- a/src/App.ts +++ b/src/App.ts @@ -56,6 +56,6 @@ export class App { } onClickMovieCard(movieId: number) { - this.#modal.open(movieId, "movieDetail"); + this.#modal.open("movieDetail", movieId); } } diff --git a/src/components/Modal/MovieDetailModal/index.ts b/src/components/Modal/MovieDetailModal/index.ts index 4595fe35c..3a0fb8b3a 100644 --- a/src/components/Modal/MovieDetailModal/index.ts +++ b/src/components/Modal/MovieDetailModal/index.ts @@ -10,6 +10,7 @@ import { $ } from "../../../utils/selector"; import { renderStars } from "./StarSelect"; import { getImageContainerTemplate } from "./Image"; import { getDescriptionTemplate } from "./Description"; +import { fetchMovieDetailById } from "../../../utils/api"; export class MovieDetailModal { #$target; @@ -35,13 +36,14 @@ export class MovieDetailModal { }); } - render(movie: MovieDetail, movieId: number) { + async render(movieId: number) { + const movieDetail = await fetchMovieDetailById(movieId); const header = $(".modal-header--text"); - if (header instanceof HTMLElement) header.textContent = movie.title; + if (header instanceof HTMLElement) header.textContent = movieDetail.title; this.#$target.innerHTML = this.getMovieDetailTemplate( - movie, + movieDetail, getStarRateFromStorage(movieId) ); } diff --git a/src/components/Modal/index.ts b/src/components/Modal/index.ts index 4a3b3ff4d..a9e683e96 100644 --- a/src/components/Modal/index.ts +++ b/src/components/Modal/index.ts @@ -2,7 +2,6 @@ import "./index.css"; import xButton from "../../../templates/xButton.png"; -import { fetchMovieDetailById } from "../../utils/api"; import { $ } from "../../utils/selector"; import { MovieDetailModal } from "./MovieDetailModal"; @@ -31,25 +30,29 @@ export class Modal { }); } - open(movieId: number, modalType: "movieDetail" | string) { + open(modalType: "movieDetail" | string, movieId: number | undefined) { const modalSection = $(".modal-section"); - if (modalType === "movieDetail") - fetchMovieDetailById(movieId).then((movieDetail) => { - this.#$movieDetail.render(movieDetail, movieId); - + if (modalType === "movieDetail" && movieId) + return this.#$movieDetail.render(movieId).then(() => { if (modalSection instanceof HTMLElement) modalSection.style.display = "block"; }); + + if (modalSection instanceof HTMLElement) + modalSection.style.display = "block"; } close() { - const modalSection = $(".modal-section"); - const modalHeader = $(".modal-header--text"); + const $modalSection = $(".modal-section"); + const $modalHeader = $(".modal-header--text"); + const $modalContent = $(".modal-content"); - if (modalSection instanceof HTMLElement) - modalSection.style.display = "none"; + if ($modalSection instanceof HTMLElement) + $modalSection.style.display = "none"; + + if ($modalHeader instanceof HTMLElement) $modalHeader.textContent = ""; - if (modalHeader instanceof HTMLElement) modalHeader.textContent = ""; + if ($modalContent instanceof HTMLElement) $modalContent.innerHTML = ""; } } From f1ce9f8b180ab40e37adaac694c479be563a27a2 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Wed, 29 Mar 2023 18:13:02 +0900 Subject: [PATCH 27/38] =?UTF-8?q?refactor:=20Header=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=B0=94?= =?UTF-8?q?=EC=9D=B8=EB=94=A9=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.ts | 34 +++++++++++++++++++--------- src/components/Header/index.ts | 41 +++++++++------------------------- 2 files changed, 34 insertions(+), 41 deletions(-) diff --git a/src/App.ts b/src/App.ts index f97b9743b..95ede253c 100644 --- a/src/App.ts +++ b/src/App.ts @@ -13,33 +13,47 @@ export class App { const $movieList = $(".item-list"); const $modal = $(".modal-content"); - this.#header = new Header( - $header, - this.onSubmitSearchKeyword.bind(this), - this.onClickLogoImage.bind(this) - ); + this.#header = new Header($header); this.#movieList = new MovieList($movieList); this.#modal = new Modal($modal); - this.bindEvent($movieList); + this.bindEvent($movieList, $header); } - bindEvent($movieList: Element) { - $movieList.addEventListener("click", (event: Event) => { - if (!(event.target instanceof HTMLElement)) return; + bindEvent($movieList: Element, $header: Element) { + $movieList.addEventListener("click", ({ target }) => { + if (!(target instanceof HTMLElement)) return; - const movieCard = event.target.closest("li"); + const movieCard = target.closest("li"); const movieId = movieCard?.dataset.movieId; if (movieId) this.onClickMovieCard(Number(movieId)); }); + + $header.addEventListener("click", ({ target }) => { + if (!(target instanceof HTMLImageElement)) return; + + this.onClickLogoImage(); + }); + + $header.addEventListener("submit", (event) => { + event.preventDefault(); + + this.onSubmitSearchKeyword(this.#header.getInputValue()); + + if (event.target instanceof HTMLFormElement) event.target.reset(); + }); } onSubmitSearchKeyword(serachKeyword: string) { const subTitle = $(".sub-title"); + if (serachKeyword === "") return alert("검색값을 입력해주세요."); + if (serachKeyword.trim().length === 0) + return alert("올바른 검색어를 입력해주세요."); + subTitle.innerHTML = `"${serachKeyword}" 검색 결과`; if (this.#movieList instanceof MovieList) diff --git a/src/components/Header/index.ts b/src/components/Header/index.ts index 7eeed644b..49b4d70c7 100644 --- a/src/components/Header/index.ts +++ b/src/components/Header/index.ts @@ -7,40 +7,10 @@ import { $ } from "../../utils/selector"; export class Header { #$target; - constructor( - $target: Element, - onSubmitSearchKeyword: (searchKeyword: string) => void, - onClickLogoImage: () => void - ) { + constructor($target: Element) { this.#$target = $target; this.render(); - this.bindEvent(onSubmitSearchKeyword, onClickLogoImage); - } - - bindEvent( - onSubmitSearchKeyword: (searchKeyword: string) => void, - onClickLogoImage: () => void - ) { - $(".search-box").addEventListener("submit", (event: Event) => { - event.preventDefault(); - - const $searchInput = $(".search-input"); - - if ($searchInput instanceof HTMLInputElement) { - const inputValue = $searchInput.value; - - if (inputValue === "") return alert("검색값을 입력해주세요."); - if (inputValue.trim().length === 0) - return alert("올바른 검색어를 입력해주세요."); - - onSubmitSearchKeyword(inputValue); - } - - if (event.target instanceof HTMLFormElement) event.target.reset(); - }); - - $(".logo").addEventListener("click", onClickLogoImage); } render() { @@ -52,4 +22,13 @@ export class Header { `; } + + getInputValue() { + const $searchInput = $(".search-input"); + + if (!($searchInput instanceof HTMLInputElement)) + throw new Error("입력창이 존재하지 않습니다."); + + return $searchInput.value; + } } From 44cbce4ab30c28a637968ad440515d94955759b2 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Wed, 29 Mar 2023 18:15:18 +0900 Subject: [PATCH 28/38] =?UTF-8?q?refactor:=20api=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 각 요청에 대한 함수 선언 => 요청 함수는 공통으로 사용, 요청 url 생성 함수 추가 --- .../Modal/MovieDetailModal/index.ts | 5 +- src/components/MovieList/MovieCard.ts | 12 +- src/components/MovieList/index.ts | 118 +++++++----------- src/utils/api.ts | 52 ++++---- 4 files changed, 85 insertions(+), 102 deletions(-) diff --git a/src/components/Modal/MovieDetailModal/index.ts b/src/components/Modal/MovieDetailModal/index.ts index 3a0fb8b3a..b4a0726f6 100644 --- a/src/components/Modal/MovieDetailModal/index.ts +++ b/src/components/Modal/MovieDetailModal/index.ts @@ -10,7 +10,7 @@ import { $ } from "../../../utils/selector"; import { renderStars } from "./StarSelect"; import { getImageContainerTemplate } from "./Image"; import { getDescriptionTemplate } from "./Description"; -import { fetchMovieDetailById } from "../../../utils/api"; +import { convertMovieDetail, getURL, request } from "../../../utils/api"; export class MovieDetailModal { #$target; @@ -37,8 +37,9 @@ export class MovieDetailModal { } async render(movieId: number) { - const movieDetail = await fetchMovieDetailById(movieId); const header = $(".modal-header--text"); + const response = await request(getURL({ movieId: movieId })); + const movieDetail = convertMovieDetail(response); if (header instanceof HTMLElement) header.textContent = movieDetail.title; diff --git a/src/components/MovieList/MovieCard.ts b/src/components/MovieList/MovieCard.ts index 57abf7c61..c18a8a68a 100644 --- a/src/components/MovieList/MovieCard.ts +++ b/src/components/MovieList/MovieCard.ts @@ -19,7 +19,17 @@ export const getMovieCardTemplate = (movie: Movie) => { /> ` : /*html */ ` -
    +
    No Image
    ` diff --git a/src/components/MovieList/index.ts b/src/components/MovieList/index.ts index f66d4464d..fe5d4bc63 100644 --- a/src/components/MovieList/index.ts +++ b/src/components/MovieList/index.ts @@ -2,8 +2,7 @@ import "./index.css"; import type { MovieResponse } from "../../types"; -import { Movies } from "../../domain/Movies"; -import { fetchPopularMovies, fetchSearchMovies } from "../../utils/api"; +import { getURL, request } from "../../utils/api"; import { $ } from "../../utils/selector"; import { deleteSkeletonContainer, @@ -14,7 +13,7 @@ import { getMovieCardTemplate } from "./MovieCard"; type showType = "popular" | "search"; -interface State { +export interface State { showState: showType; searchKeyword: string; page: number; @@ -29,113 +28,82 @@ export class MovieList { page: 1, }; - #movies: Movies = new Movies([]); - constructor($target: Element) { this.#$target = $target; this.init(); } - init() { + async init() { this.#$target.insertAdjacentElement("afterend", getSkeletonContainer()); - fetchPopularMovies(this.#state.page) - .then((response) => { - const { results, total_pages } = response; + try { + const { results, total_pages } = await request( + getURL({ state: this.#state }) + ); - this.#movies.reset(results); - this.render(results, total_pages); + this.render(results, total_pages); - new IntersectionObserver(this.fetchNextPage.bind(this)).observe( - $(".btn") - ); - }) - .catch(() => { - deleteSkeletonContainer(); - }); + new IntersectionObserver(this.fetchNextPage.bind(this)).observe( + $(".btn") + ); + } catch { + deleteSkeletonContainer(); + } } render(movieList: MovieResponse[], total_pages: number) { deleteSkeletonContainer(); - if (this.#state.page !== 1) this.#movies.add(movieList); - this.#$target.insertAdjacentHTML( "beforeend", - `${this.#movies - .getCurrentList() - .map((movie) => getMovieCardTemplate(movie)) - .join("")} + `${movieList.map((movie) => getMovieCardTemplate(movie)).join("")} ` ); if (this.#state.page === total_pages) this.deactivateScrollFetch(); + + if (this.#state.page === 1 && movieList.length === 0) { + const subTitle = $(".sub-title"); + + subTitle.innerHTML = "검색 결과 없음"; + } } - changeShowTarget(state: showType, searchKeyword?: string) { + async changeShowTarget(state: showType, searchKeyword?: string) { this.#$target.innerHTML = ``; - this.#state = { ...this.#state, showState: state, page: 1 }; + this.#state = { + ...this.#state, + showState: state, + page: 1, + searchKeyword: searchKeyword ?? "", + }; this.activateScrollFetch(); showSkeletonContainer(); - if (state === "popular") { - fetchPopularMovies(this.#state.page) - .then((response) => { - const { results, total_pages } = response; - - this.#movies.reset(results); - this.render(results, total_pages); - }) - .catch(() => { - deleteSkeletonContainer(); - }); - - return; - } - - if (searchKeyword) { - this.#state = { ...this.#state, searchKeyword: searchKeyword }; + const { results, total_pages } = await request( + getURL({ state: this.#state }) + ); - fetchSearchMovies(this.#state.page, this.#state.searchKeyword) - .then((response) => { - const { results, total_pages } = response; + this.render(results, total_pages); - this.#movies.reset(results); - this.render(results, total_pages); - }) - .catch(() => { - deleteSkeletonContainer(); - }); - } + deleteSkeletonContainer(); } - fetchNextPage() { + async fetchNextPage() { this.#state.page += 1; showSkeletonContainer(); - if (this.#state.showState === "popular") - fetchPopularMovies(this.#state.page) - .then((response) => { - const { results, total_pages } = response; - - this.render(results, total_pages); - }) - .catch(() => { - deleteSkeletonContainer(); - }); - - if (this.#state.showState === "search") - fetchSearchMovies(this.#state.page, this.#state.searchKeyword) - .then((response) => { - const { results, total_pages } = response; - - this.render(results, total_pages); - }) - .catch(() => { - deleteSkeletonContainer(); - }); + try { + const { results, total_pages } = await request( + getURL({ state: this.#state }) + ); + + this.render(results, total_pages); + } catch { + deleteSkeletonContainer(); + } } deactivateScrollFetch() { diff --git a/src/utils/api.ts b/src/utils/api.ts index 30c8a2625..21aca9a19 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -1,8 +1,15 @@ import type { MovieDetail, MovieDetailResponse } from "../types"; +import { State } from "../components/MovieList"; + +interface UrlParam { + state?: State; + movieId?: number; +} + const API_END_POINT = "https://api.themoviedb.org/3"; -const request = async (url: string) => { +export const request = async (url: string) => { try { const response = await fetch(url); @@ -12,12 +19,31 @@ const request = async (url: string) => { throw new Error(`${response.status}`); } catch (error: any) { - if (error.message === "Failed to fatch") + if (error.message === "Failed to fetch") return alert("네트워크 연결이 종료되었습니다."); + alert(`${error.message} 에러가 발생했습니다!`); } }; +export const getURL = (urlParam: UrlParam) => { + if (urlParam.movieId) + return `${API_END_POINT}/movie/${urlParam.movieId}?api_key=${process.env.API_KEY}&language=ko`; + + if (urlParam.state) { + if (urlParam.state.showState === "popular") + return `${API_END_POINT}/movie/popular?api_key=${process.env.API_KEY}&language=ko&page=${urlParam.state.page}`; + if (urlParam.state.showState === "search") + return `${API_END_POINT}/search/movie?api_key=${ + process.env.API_KEY + }&language=ko&page=${urlParam.state.page}&query=${encodeURI( + urlParam.state.searchKeyword + )}`; + } + + throw new Error("url을 만들기 위한 올바른 인자값이 전달되지 않았습니다."); +}; + export const convertMovieDetail = ( movieDetail: MovieDetailResponse ): MovieDetail => { @@ -30,25 +56,3 @@ export const convertMovieDetail = ( overview: movieDetail.overview, }; }; - -export const fetchPopularMovies = (page: number) => { - const url = `${API_END_POINT}/movie/popular?api_key=${process.env.API_KEY}&language=ko&page=${page}`; - - return request(url); -}; - -export const fetchSearchMovies = (page: number, keyword: string) => { - const url = `${API_END_POINT}/search/movie?api_key=${ - process.env.API_KEY - }&language=ko&page=${page}&query=${encodeURI(keyword)}`; - - return request(url); -}; - -export const fetchMovieDetailById = async (movieId: number) => { - const url = `${API_END_POINT}/movie/${movieId}?api_key=${process.env.API_KEY}&language=ko`; - const response = await request(url); - const movieDetail = convertMovieDetail(response); - - return movieDetail; -}; From 54bb3b697903509d4044eba92a28e30b56101998 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 31 Mar 2023 09:25:31 +0900 Subject: [PATCH 29/38] =?UTF-8?q?refactor:=20=EC=A7=81=EA=B4=80=EC=A0=81?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/api.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/utils/api.ts b/src/utils/api.ts index 21aca9a19..6157c3df0 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -19,10 +19,19 @@ export const request = async (url: string) => { throw new Error(`${response.status}`); } catch (error: any) { - if (error.message === "Failed to fetch") - return alert("네트워크 연결이 종료되었습니다."); + if (error.message === "Failed to fetch") { + alert("네트워크 연결이 종료되었습니다."); + return; + } - alert(`${error.message} 에러가 발생했습니다!`); + switch (error.message.slice(0, 1)) { + case "4": + alert("잘못된 요청입니다!"); + break; + case "5": + alert("서버에서 오류가 발생했습니다."); + break; + } } }; From fdab5dedcbb736a54e95e6805f15415ab8227875 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 31 Mar 2023 09:52:50 +0900 Subject: [PATCH 30/38] =?UTF-8?q?style:=20=EB=A1=9C=EC=BB=AC=20=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=20=EC=9C=A0=ED=8B=B8=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20early=20return=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/storage.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/utils/storage.ts b/src/utils/storage.ts index b611347e4..d7b5cdb93 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -13,16 +13,15 @@ export const getStarRateFromStorage = (movieId: number) => { export const setStarRateToStorage = (movieId: number, starRate: number) => { const storageData = localStorage.getItem("starRate"); - if (storageData) { - const json = JSON.parse(storageData); - - localStorage.setItem( - "starRate", - JSON.stringify({ ...json, [movieId]: starRate }) - ); - + if (!storageData) { + localStorage.setItem("starRate", JSON.stringify({ [movieId]: starRate })); return; } - localStorage.setItem("starRate", JSON.stringify({ [movieId]: starRate })); + const json = JSON.parse(storageData); + + localStorage.setItem( + "starRate", + JSON.stringify({ ...json, [movieId]: starRate }) + ); }; From 6d76b8647ed51bdd00389f6b355d7a67f3964db7 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 31 Mar 2023 09:59:31 +0900 Subject: [PATCH 31/38] =?UTF-8?q?refactor:=20css=EC=97=90=EC=84=9C=20!impo?= =?UTF-8?q?rtant=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/style/common.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/style/common.css b/src/style/common.css index 1abd4a267..d1b173396 100644 --- a/src/style/common.css +++ b/src/style/common.css @@ -1,5 +1,5 @@ * { - box-sizing: border-box !important; + box-sizing: border-box; } body { From a1b203ccfcd64d8308eb28f9847c43c273722573 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 31 Mar 2023 10:01:49 +0900 Subject: [PATCH 32/38] =?UTF-8?q?style:=20map=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=B6=95=EC=95=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/MovieList/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MovieList/index.ts b/src/components/MovieList/index.ts index fe5d4bc63..ab85c5e76 100644 --- a/src/components/MovieList/index.ts +++ b/src/components/MovieList/index.ts @@ -57,7 +57,7 @@ export class MovieList { this.#$target.insertAdjacentHTML( "beforeend", - `${movieList.map((movie) => getMovieCardTemplate(movie)).join("")} + `${movieList.map(getMovieCardTemplate).join("")} ` ); From c86d95391a78be49d30e2f52b9ba11de284997dc Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 31 Mar 2023 10:22:36 +0900 Subject: [PATCH 33/38] =?UTF-8?q?style:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/MovieList/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/MovieList/index.ts b/src/components/MovieList/index.ts index ab85c5e76..29384d667 100644 --- a/src/components/MovieList/index.ts +++ b/src/components/MovieList/index.ts @@ -61,7 +61,7 @@ export class MovieList { ` ); - if (this.#state.page === total_pages) this.deactivateScrollFetch(); + if (this.#state.page === total_pages) this.hideMore(); if (this.#state.page === 1 && movieList.length === 0) { const subTitle = $(".sub-title"); @@ -79,7 +79,7 @@ export class MovieList { searchKeyword: searchKeyword ?? "", }; - this.activateScrollFetch(); + this.showMore(); showSkeletonContainer(); const { results, total_pages } = await request( @@ -106,11 +106,11 @@ export class MovieList { } } - deactivateScrollFetch() { + hideMore() { $(".btn").setAttribute("hidden", ""); } - activateScrollFetch() { + showMore() { $(".btn").removeAttribute("hidden"); } } From 118bff8847edd2482b13875bae1b4f24850acf6f Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 31 Mar 2023 10:27:26 +0900 Subject: [PATCH 34/38] =?UTF-8?q?style:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20im?= =?UTF-8?q?port=EC=8B=9C=20"Image"=20suffix=20=EC=B6=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Modal/MovieDetailModal/Description.ts | 4 ++-- src/components/Modal/MovieDetailModal/StarSelect.ts | 6 +++--- src/components/Modal/index.ts | 4 ++-- src/components/MovieList/MovieCard.ts | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/Modal/MovieDetailModal/Description.ts b/src/components/Modal/MovieDetailModal/Description.ts index d0dd38e95..a4e711b72 100644 --- a/src/components/Modal/MovieDetailModal/Description.ts +++ b/src/components/Modal/MovieDetailModal/Description.ts @@ -1,6 +1,6 @@ import type { MovieDetail } from "../../../types"; -import filledStarImg from "../../../../templates/star_filled.png"; +import filledStarImage from "../../../../templates/star_filled.png"; import { getStarSelectContainerTemplate } from "./StarSelect"; @@ -16,7 +16,7 @@ export function getDescriptionTemplate(movie: MovieDetail, starRate: number) { } 별점 ${movie.vote_average} ${movie.vote_average.toFixed(1)} diff --git a/src/components/Modal/MovieDetailModal/StarSelect.ts b/src/components/Modal/MovieDetailModal/StarSelect.ts index c1e7c78a3..01f82dfcc 100644 --- a/src/components/Modal/MovieDetailModal/StarSelect.ts +++ b/src/components/Modal/MovieDetailModal/StarSelect.ts @@ -1,5 +1,5 @@ -import filledStarImg from "../../../../templates/star_filled.png"; -import emptyStarImg from "../../../../templates/star_empty.png"; +import filledStarImage from "../../../../templates/star_filled.png"; +import emptyStarImage from "../../../../templates/star_empty.png"; import { $ } from "../../../utils/selector"; @@ -43,7 +43,7 @@ export function getStarTemplate(movieId: number, starRate: number) { { length: 5 }, (_, i) => `별점 { return ( @@ -36,7 +36,7 @@ export const getMovieCardTemplate = (movie: Movie) => { }

    ${movie.title}

    - 별점 ${movie.vote_average} + 별점 ${movie.vote_average} ${movie.vote_average}

    From ef66deffe2ed357be3c9706da46f98192d7f868d Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 31 Mar 2023 10:27:26 +0900 Subject: [PATCH 35/38] =?UTF-8?q?style:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20im?= =?UTF-8?q?port=EC=8B=9C=20"Image"=20suffix=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Modal/MovieDetailModal/Description.ts | 4 ++-- src/components/Modal/MovieDetailModal/StarSelect.ts | 6 +++--- src/components/Modal/index.ts | 4 ++-- src/components/MovieList/MovieCard.ts | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/Modal/MovieDetailModal/Description.ts b/src/components/Modal/MovieDetailModal/Description.ts index d0dd38e95..a4e711b72 100644 --- a/src/components/Modal/MovieDetailModal/Description.ts +++ b/src/components/Modal/MovieDetailModal/Description.ts @@ -1,6 +1,6 @@ import type { MovieDetail } from "../../../types"; -import filledStarImg from "../../../../templates/star_filled.png"; +import filledStarImage from "../../../../templates/star_filled.png"; import { getStarSelectContainerTemplate } from "./StarSelect"; @@ -16,7 +16,7 @@ export function getDescriptionTemplate(movie: MovieDetail, starRate: number) { } 별점 ${movie.vote_average} ${movie.vote_average.toFixed(1)} diff --git a/src/components/Modal/MovieDetailModal/StarSelect.ts b/src/components/Modal/MovieDetailModal/StarSelect.ts index c1e7c78a3..01f82dfcc 100644 --- a/src/components/Modal/MovieDetailModal/StarSelect.ts +++ b/src/components/Modal/MovieDetailModal/StarSelect.ts @@ -1,5 +1,5 @@ -import filledStarImg from "../../../../templates/star_filled.png"; -import emptyStarImg from "../../../../templates/star_empty.png"; +import filledStarImage from "../../../../templates/star_filled.png"; +import emptyStarImage from "../../../../templates/star_empty.png"; import { $ } from "../../../utils/selector"; @@ -43,7 +43,7 @@ export function getStarTemplate(movieId: number, starRate: number) { { length: 5 }, (_, i) => `별점 { return ( @@ -36,7 +36,7 @@ export const getMovieCardTemplate = (movie: Movie) => { }

    ${movie.title}

    - 별점 ${movie.vote_average} + 별점 ${movie.vote_average} ${movie.vote_average}

    From 34e0c0a698e90128fd2a8be481cd5118dd046fad Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 31 Mar 2023 10:31:22 +0900 Subject: [PATCH 36/38] =?UTF-8?q?refactor:=20=EB=AA=A8=EB=8B=AC=20open?= =?UTF-8?q?=EC=8B=9C=20=EC=82=AC=EC=9A=A9=EB=90=98=EB=8A=94=20modalType=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 --- src/components/Modal/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Modal/index.ts b/src/components/Modal/index.ts index 49d700ea9..136622fb1 100644 --- a/src/components/Modal/index.ts +++ b/src/components/Modal/index.ts @@ -30,7 +30,7 @@ export class Modal { }); } - open(modalType: "movieDetail" | string, movieId: number | undefined) { + open(modalType: "movieDetail", movieId: number | undefined) { const modalSection = $(".modal-section"); if (modalType === "movieDetail" && movieId) From c8517f2ccffbac3e201e71f562775e906bacf37f Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 31 Mar 2023 10:46:05 +0900 Subject: [PATCH 37/38] =?UTF-8?q?refactor:=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=ED=9B=84=20input=EC=B0=BD=20=EB=A6=AC=EC=85=8B=20=3D>=20?= =?UTF-8?q?=EB=A6=AC=EC=85=8B=20=EC=8B=9C=ED=82=A4=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EA=B3=A0,=20input=EC=B0=BD=20=ED=81=B4=EB=A6=AD=EC=8B=9C=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.ts | 2 -- src/components/Header/index.ts | 11 +++++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/App.ts b/src/App.ts index 95ede253c..5e2cb73df 100644 --- a/src/App.ts +++ b/src/App.ts @@ -42,8 +42,6 @@ export class App { event.preventDefault(); this.onSubmitSearchKeyword(this.#header.getInputValue()); - - if (event.target instanceof HTMLFormElement) event.target.reset(); }); } diff --git a/src/components/Header/index.ts b/src/components/Header/index.ts index 49b4d70c7..0d35f8dc4 100644 --- a/src/components/Header/index.ts +++ b/src/components/Header/index.ts @@ -11,6 +11,7 @@ export class Header { this.#$target = $target; this.render(); + this.bindEvent(); } render() { @@ -23,6 +24,16 @@ export class Header { `; } + bindEvent() { + const $searchInput = $(".search-input"); + + if (!($searchInput instanceof HTMLInputElement)) return; + + $searchInput.addEventListener("click", function () { + this.select(); + }); + } + getInputValue() { const $searchInput = $(".search-input"); From b5f09e865753da79237e86c92f0de9f1da70f9c2 Mon Sep 17 00:00:00 2001 From: kyw0716 Date: Fri, 31 Mar 2023 18:11:04 +0900 Subject: [PATCH 38/38] =?UTF-8?q?refactor:=20=EB=B3=84=EC=A0=90=200?= =?UTF-8?q?=EC=A0=90=20=EC=84=A0=ED=83=9D=20=EA=B8=B0=EB=8A=A5=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 --- .../Modal/MovieDetailModal/StarSelect.ts | 10 ++++++++-- src/components/Modal/MovieDetailModal/index.ts | 18 +++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/components/Modal/MovieDetailModal/StarSelect.ts b/src/components/Modal/MovieDetailModal/StarSelect.ts index 01f82dfcc..6a4ed94c0 100644 --- a/src/components/Modal/MovieDetailModal/StarSelect.ts +++ b/src/components/Modal/MovieDetailModal/StarSelect.ts @@ -29,7 +29,13 @@ export function getStarSelectContainerTemplate( const imgArray = getStarTemplate(movieId, starRate); return /*html*/ ` - 내 별점 + + 내 별점 + ${imgArray.join("")} @@ -47,7 +53,7 @@ export function getStarTemplate(movieId: number, starRate: number) { alt="별점" class="star-rate-select-img" data-movie-id="${movieId}" - data-star-rate="${i}" + data-star-rate="${i + 1}" />` ); } diff --git a/src/components/Modal/MovieDetailModal/index.ts b/src/components/Modal/MovieDetailModal/index.ts index b4a0726f6..ceab71344 100644 --- a/src/components/Modal/MovieDetailModal/index.ts +++ b/src/components/Modal/MovieDetailModal/index.ts @@ -23,11 +23,23 @@ export class MovieDetailModal { bindEvent() { $(".modal-content").addEventListener("mouseover", (event: Event) => { - if (!(event.target instanceof HTMLImageElement)) return; - if (event.target.className !== "star-rate-select-img") return; + if ( + !( + event.target instanceof HTMLImageElement || + event.target instanceof HTMLSpanElement + ) + ) + return; + if ( + !( + event.target.className === "star-rate-select-img" || + event.target.className === "select-zero-rate" + ) + ) + return; const movieId = Number(event.target.dataset.movieId); - const starRate = Number(event.target.dataset.starRate) + 1; + const starRate = Number(event.target.dataset.starRate); if (getStarRateFromStorage(movieId) !== starRate) { renderStars(movieId, starRate);