diff --git a/mission004/ganeodolu/css/style.css b/mission004/ganeodolu/css/style.css new file mode 100644 index 0000000..12a5f89 --- /dev/null +++ b/mission004/ganeodolu/css/style.css @@ -0,0 +1,247 @@ +* { + margin: 0; + padding: 0; + color: #fff; + -webkit-box-sizing: border-box; + box-sizing: border-box; + border: 0; + font-size: 100%; + font-style: normal; +} + +*:focus { + outline: none; +} + +html, +body { + width: 100%; + height: 100%; +} + +body { + background-color: #020e2f; + color: #fff; + height: 100%; +} + +ol, +ul { + list-style: none; +} + +blockquote, +q { + quotes: none; +} + +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ""; + content: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +button { + display: inline-block; + background: none; + border: none; + outline: none; + cursor: pointer; + vertical-align: middle; +} + +button:disabled, +button[disabled] { + cursor: initial; +} + +select { + border: none; + background-color: transparent; +} + +a { + text-decoration: none; + color: inherit; +} + +.header { + box-shadow: rgba(0, 0, 0, 0.5) 0px 2px 6px 2px; +} + +.header-cont { + height: 78px; + max-width: 1200px; + padding: 0 24px; +} + +.logo { + position: absolute; + font-weight: bold; + font-size: 20px; + top: 26px; +} + +.search-bar { + position: absolute; + height: 24px; + right: 24px; + top: 20px; + width: 240px; +} + +.input-search { + width: 210px; + height: 24px; + background-color: transparent; + border-bottom: 2px solid #fff; +} + +.main { + padding: 84px 36px; + text-align: center; +} + +.movie-list { + text-align: left; + margin: 0 auto; + width: 1024px; +} + +.movie-list-item { + width: 180px; + margin: 0 20px 30px 0; + height: 340px; + display: inline-block; + vertical-align: top; + position: relative; + cursor: pointer; +} + +.movie-list-item:hover { + top: 1px; + left: 1px; +} + +.movie-list-item:hover .movie-list-dim { + opacity: 0.5; +} + +.movie-list-dim { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + background: -webkit-gradient( + linear, + left top, left bottom, + from(rgba(0, 0, 0, 0.3)), + to(rgba(0, 0, 0, 1)) + ); + background: linear-gradient( + 180deg, + rgba(0, 0, 0, 0.3) 0%, + rgba(0, 0, 0, 1) 100% + ); + opacity: 0; + transition: opacity 0.3s ease-in-out; +} + +.poster { + width: 180px; + height: 270px; + -webkit-box-shadow: 3px 3px 9px 0px rgba(0, 0, 0, 0.4); + box-shadow: 3px 3px 9px 0px rgba(0, 0, 0, 0.4); +} + +.movie-info { + margin-top: 4px; +} + +.movie-info li { + font-size: 14px; +} + +.movie-info li:last-of-type { + font-size: 12px; + opacity: 0.8; +} + +.movie-title { + font-weight: bold; + margin-bottom: 4px; + font-size: 16px; +} + +/*상세 페이지 */ +.main-detail { + display: flex; + height: calc(100% - 82px); +} + +.detail-bg { + position: fixed; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + background-image: url(https://image.tmdb.org/t/p/w300/lMZyfDVoxhmfBUNmnNQWvSEL9E3.jpg); + background-size: cover; + filter: blur(3px); + opacity: 0.5; + background-position: center center; + z-index: -1; +} + +.detail-poster { + border-radius: 20px; + width: auto; + height: 100%; +} + +.detail-info { + padding-left: 24px; + text-align: left; +} + +.detail-info h1 { + font-size: 36px; + margin-bottom: 12px; + font-weight: 300; +} + +.info-list li { + display: inline-block; + position: relative; + margin-right: 16px; +} + +.info-list li:after { + content: '.'; + position: absolute; + top: -6px; + right: -12px; +} + +.info-list li:last-of-type:after { + content: ''; +} + +.info-list .txt-14 { + font-size: 14px; + opacity: 0.8; +} + +.overview { + margin-top: 24px; + font-size: 14px; + opacity: 0.8; +} \ No newline at end of file diff --git a/mission004/ganeodolu/html/detail.html b/mission004/ganeodolu/html/detail.html new file mode 100644 index 0000000..093bfc0 --- /dev/null +++ b/mission004/ganeodolu/html/detail.html @@ -0,0 +1,29 @@ + + + + + + + MISSION 4 + + + + + +
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/mission004/ganeodolu/index.html b/mission004/ganeodolu/index.html new file mode 100644 index 0000000..a2b8a83 --- /dev/null +++ b/mission004/ganeodolu/index.html @@ -0,0 +1,29 @@ + + + + + + MISSION 4 + + + + +
+
+ + +
+
+
+
+ + + diff --git a/mission004/ganeodolu/js/app.js b/mission004/ganeodolu/js/app.js new file mode 100644 index 0000000..73ccf12 --- /dev/null +++ b/mission004/ganeodolu/js/app.js @@ -0,0 +1,60 @@ +import { fetchGetMovie, fetchSearchMovie } from '../util/api.js' +import GetMovieList from './getMovieList.js' +import SearchMovie from './searchMovie.js' +import ShowMovieList from './showMovieList.js' +import ScrollMovieList from './scrollMovieList.js' + +export default function App() { + + const $targetMovieList = document.querySelector('.main') + const $targetLogo = document.querySelector('.logo') + const $targetSearch = document.querySelector('.input-search') + const $targetSearchIcon = document.querySelector('.material-icons') + + $targetLogo.addEventListener('click', (e) => { + sessionStorage.removeItem('getKeyword') + window.location.reload() + }) + + const getMovieList = new GetMovieList({ + onLoad: async () => { + let keyword = sessionStorage.getItem('getKeyword') + if (!keyword) { + const data = await fetchGetMovie() + showMovieList.setState(data) + } else { + const data = await fetchSearchMovie(1, keyword) + showMovieList.setState(data) + } + } + }) + + const showMovieList = new ShowMovieList({ + $targetMovieList: $targetMovieList, + data: [], + }) + + const searchMovie = new SearchMovie({ + $targetSearch: $targetSearch, + $targetSearchIcon: $targetSearchIcon, + onClickSearch: async (keyword) => { + const data = await fetchSearchMovie(1, keyword) + showMovieList.setState(data) + } + }) + + const scrollMovieList = new ScrollMovieList({ + onScroll: async (pageNumber, keyword) => { + if (!keyword) { + const data = await fetchGetMovie(pageNumber) + showMovieList.addSetState(data) + } else { + const data = await fetchSearchMovie(pageNumber, keyword) + showMovieList.addSetState(data) + } + } + }) +} + +new App() + diff --git a/mission004/ganeodolu/js/app_detail.js b/mission004/ganeodolu/js/app_detail.js new file mode 100644 index 0000000..89d444e --- /dev/null +++ b/mission004/ganeodolu/js/app_detail.js @@ -0,0 +1,46 @@ +import ShowMovieDetail from './showMovieDetail.js' +import GetMovieDetail from './getMovieDetail.js' +import { fetchGetMovieDetail } from '../util/api.js' +import { KEY_NAME } from '../util/constant.js' + +export default function App_detail() { + + const $targetDetail = document.querySelector('.main') + const $targetLogo = document.querySelector('.logo') + const $targetSearch = document.querySelector('.input-search') + const $targetSearchIcon = document.querySelector('.material-icons') + + + $targetLogo.addEventListener('click', (e) => { + window.location.href = '../index.html'; + }) + + const getMovieDetail = new GetMovieDetail({ + onLoad: async () => { + let movieId = Number(sessionStorage.getItem('getMovieId')) + const data = await fetchGetMovieDetail(movieId) + showMovieDetail.setState(data) + } + }) + + const showMovieDetail = new ShowMovieDetail({ + $targetDetail: $targetDetail, + data: [] + }) + + $targetSearch.addEventListener('keypress', (e) => { + if (e.key === KEY_NAME.ENTER) { + let getKeyword = e.target.value + sessionStorage.setItem('getKeyword', getKeyword) + window.location.href = '../index.html'; + } + }) + $targetSearchIcon.addEventListener('click', (e) => { + let getKeyword = e.target.previousElementSibling.value + sessionStorage.setItem('getKeyword', getKeyword) + window.location.href = '../index.html'; + }) +} + +new App_detail() + diff --git a/mission004/ganeodolu/js/getMovieDetail.js b/mission004/ganeodolu/js/getMovieDetail.js new file mode 100644 index 0000000..61b2bfd --- /dev/null +++ b/mission004/ganeodolu/js/getMovieDetail.js @@ -0,0 +1,5 @@ +export default function GetMovieDetail({onLoad}){ + window.addEventListener('load', (e) => { + onLoad() + }) +} \ No newline at end of file diff --git a/mission004/ganeodolu/js/getMovieList.js b/mission004/ganeodolu/js/getMovieList.js new file mode 100644 index 0000000..08071a8 --- /dev/null +++ b/mission004/ganeodolu/js/getMovieList.js @@ -0,0 +1,5 @@ +export default function GetMovieList({ onLoad }){ + window.addEventListener('load', (e) => { + onLoad() + }) +} \ No newline at end of file diff --git a/mission004/ganeodolu/js/scrollMovieList.js b/mission004/ganeodolu/js/scrollMovieList.js new file mode 100644 index 0000000..aa52b6d --- /dev/null +++ b/mission004/ganeodolu/js/scrollMovieList.js @@ -0,0 +1,19 @@ +export default function ScrollMovieList({ onScroll }) { + + let pageNumber = 1; + let scrollTimer; + + window.onscroll = async function (e) { + + if ((window.pageYOffset + document.body.clientHeight) >= document.body.scrollHeight * 0.95) { + if (!scrollTimer) { + scrollTimer = setTimeout(async function () { + scrollTimer = null; + pageNumber++ + let searchKeyword = sessionStorage.getItem('getKeyword') + onScroll(pageNumber, searchKeyword) + }, 200) + } + } + } +} \ No newline at end of file diff --git a/mission004/ganeodolu/js/searchMovie.js b/mission004/ganeodolu/js/searchMovie.js new file mode 100644 index 0000000..7625c99 --- /dev/null +++ b/mission004/ganeodolu/js/searchMovie.js @@ -0,0 +1,24 @@ +export default function SearchMovie({ $targetSearch, $targetSearchIcon, onClickSearch }) { + + $targetSearch.addEventListener('keypress', async (e) => { + if (e.key === 'Enter') { + let searchKeyword = e.target.value + if (searchKeyword) { + onClickSearch(searchKeyword) + sessionStorage.setItem('getKeyword', searchKeyword) + } + } + }) + + $targetSearchIcon.addEventListener('click', (e) => { + searchKeyword = e.target.previousElementSibling.value + if (searchKeyword) { + onClickSearch(searchKeyword) + sessionStorage.setItem('getKeyword', searchKeyword) + } + }) + + $targetSearch.addEventListener('focus', (e) => { + e.target.value = '' + }) +} \ No newline at end of file diff --git a/mission004/ganeodolu/js/showMovieDetail.js b/mission004/ganeodolu/js/showMovieDetail.js new file mode 100644 index 0000000..9d00b1c --- /dev/null +++ b/mission004/ganeodolu/js/showMovieDetail.js @@ -0,0 +1,16 @@ +import { renderedMovieDetailHTML } from '../util/template.js' + +export default function ShowMovieDetail({ $targetDetail, data }) { + this.$targetDetail = $targetDetail + this.data = data + + this.setState = function (nextData) { + this.data = nextData; + this.render() + } + + this.render = function () { + this.$targetDetail.innerHTML = renderedMovieDetailHTML(this.data) + document.querySelector('.detail-bg').style.backgroundImage = `url(https://image.tmdb.org/t/p/w300${this.data.backdrop_path})` + } +} \ No newline at end of file diff --git a/mission004/ganeodolu/js/showMovieList.js b/mission004/ganeodolu/js/showMovieList.js new file mode 100644 index 0000000..b5540ea --- /dev/null +++ b/mission004/ganeodolu/js/showMovieList.js @@ -0,0 +1,37 @@ +import { renderedMovieListHTML } from '../util/template.js' +import { ERROR_TYPE } from '../util/constant.js' + +export default function ShowMovieList({ $targetMovieList, data }) { + this.$targetMovieList = $targetMovieList + this.data = data + + this.setState = function (nextData) { + this.data = nextData; + this.render() + } + this.render = function () { + if (typeof (this.data) !== 'object') { + throw new Error(ERROR_TYPE.NOT_OBJECT) + } + if (this.data.results.length === 0) { + this.$targetMovieList.innerHTML = ERROR_TYPE.NO_RESULT + } + else { + this.$targetMovieList.innerHTML = renderedMovieListHTML(this.data) + } + } + + this.addSetState = function (nextData) { + this.data = nextData; + this.addRender() + } + this.addRender = function () { + if (typeof (this.data) === 'object' && this.data.results.length > 0) { + this.$targetMovieList.insertAdjacentHTML('beforeend', renderedMovieListHTML(this.data)) + } + } + $targetMovieList.addEventListener('click', (e) => { + let getMovieId = e.target.dataset.movieid + sessionStorage.setItem('getMovieId', getMovieId) + }) +} \ No newline at end of file diff --git a/mission004/ganeodolu/package.json b/mission004/ganeodolu/package.json new file mode 100644 index 0000000..359e985 --- /dev/null +++ b/mission004/ganeodolu/package.json @@ -0,0 +1,11 @@ +{ + "name": "origin", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/mission004/ganeodolu/util/api.js b/mission004/ganeodolu/util/api.js new file mode 100644 index 0000000..874fdbe --- /dev/null +++ b/mission004/ganeodolu/util/api.js @@ -0,0 +1,27 @@ +import { MOVIE_API } from './constant.js' + +function fetchGetMovie(pageNumber) { + return new Promise((resolve, reject) => { + fetch(`${MOVIE_API.URI}movie/now_playing?${MOVIE_API.KEY}&language=en-US&page=${pageNumber}®ion=KR&include_adult=false`) + .then(res => res.json()) + .then(data => resolve(data)) + }) +} + +function fetchGetMovieDetail(movieId) { + return new Promise((resolve, reject) => { + fetch(`${MOVIE_API.URI}movie/${movieId}?${MOVIE_API.KEY}&language=en-US`) + .then(res => res.json()) + .then(data => resolve(data)) + }) +} + +function fetchSearchMovie(pageNumber, keyword) { + return new Promise((resolve, reject) => { + fetch(`${MOVIE_API.URI}search/movie?${MOVIE_API.KEY}&language=en-US&query=${keyword}&page=${pageNumber}&include_adult=false`) + .then(res => res.json()) + .then(data => resolve(data)) + }) +} + +export { fetchGetMovie, fetchGetMovieDetail, fetchSearchMovie } \ No newline at end of file diff --git a/mission004/ganeodolu/util/constant.js b/mission004/ganeodolu/util/constant.js new file mode 100644 index 0000000..febdb3d --- /dev/null +++ b/mission004/ganeodolu/util/constant.js @@ -0,0 +1,15 @@ +const MOVIE_API = { + URI: 'https://api.themoviedb.org/3/', + KEY: 'api_key=9c75b9a50b9510e81c6bc16c1a41517c' +} + +const KEY_NAME = { + ENTER: 'Enter' +} + +const ERROR_TYPE = { + NOT_OBJECT: '입력값이 객체가 아닙니다.', + NO_RESULT: '<검색 결과가 존재하지 않습니다>' +} + +export { MOVIE_API, KEY_NAME, ERROR_TYPE } \ No newline at end of file diff --git a/mission004/ganeodolu/util/template.js b/mission004/ganeodolu/util/template.js new file mode 100644 index 0000000..46da64d --- /dev/null +++ b/mission004/ganeodolu/util/template.js @@ -0,0 +1,57 @@ +function renderedMovieListHTML(inputValue) { + let result = inputValue.results.map((val, idx) => { + return ` + +
  • + +
    + +
  • +
    + ` + }).join('') + return result +} + +function renderedMovieDetailHTML(inputValue) { + let genresSum = detailSum(inputValue, 'genres', 'name') + let countrySum = detailSum(inputValue, 'production_countries', 'name') + return ` +
    + +
    +

    ${inputValue.title}

    + +

    ${inputValue.overview}

    +
    + ` +} + +function detailSum(result, prop1, prop2) { + let answer = result[prop1].map((val) => { + return val[prop2] + }).join(' / ') + return answer +} + +export { renderedMovieListHTML, renderedMovieDetailHTML } \ No newline at end of file