-
Notifications
You must be signed in to change notification settings - Fork 88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[2단계 - 상세 정보 & UI/UX 개선하기] 초코(강다빈) 미션 제출합니다. #171
Changes from 81 commits
58a3ce0
5661a8a
022dbf2
f3135ea
52f9c3a
553471b
a9e23b3
a12194e
40846a2
00f7a69
dab4109
c67458d
716b456
4e07ad5
5057566
2bbc6f4
da29495
f74a933
0b51801
568b3c4
e2b5ccc
5622b4c
0696960
04750f0
1ca0616
97c0763
98ee73b
22dc6ad
b09291f
3755207
8cf6096
fa81b18
6d4fef8
4273068
a817135
a96aac3
11532ab
02db706
75004f9
28af29a
88c2cc7
acda9b6
b27638f
ea1b8a3
7b4d067
2094ff8
83c4cd4
24db597
9cb0f27
e2f6ecd
5399034
64dd479
065f2c6
a6111ca
cefc87f
f109faa
2cc746c
1b4ea09
0fbc146
1c2c72e
af8d8e8
b1e8768
28af320
5569bdb
ccb81ff
afa3b94
0148e13
7207d52
5fb1acf
73a10cf
2d77149
c1a406b
46943e3
15111d6
2dc4077
779cebb
485910f
7e3748e
de041e3
dc31890
8209206
1c9ff19
6c861dd
4eaa732
6707341
b64fdfc
6864ff6
4dac6b6
0330025
973037a
9709602
a1a8443
9858971
8cc5075
06cad8e
48726d8
5cdec22
4e169ca
bff9d97
c846053
4542fb2
722cb70
2fadb45
cb164ab
fa80a28
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
node_modules | ||
dist | ||
.env | ||
cypress.env.json |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<!doctype html> | ||
<html lang="ko"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
|
||
<title>영화관</title> | ||
<script defer type="module" src="./dist/bundle.js"></script> | ||
<script defer src="bundle.js"></script></head> | ||
<body> | ||
<div id="app"> | ||
<header> | ||
<h1> | ||
<button id="home-button"></button> | ||
</h1> | ||
</header> | ||
<main> | ||
<section class="item-view"> | ||
<h2></h2> | ||
<ul class="item-list"></ul> | ||
</section> | ||
</main> | ||
</div> | ||
</body> | ||
</html> |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,57 @@ | ||
import { Movie } from './index.d'; | ||
|
||
import { SKELETON_UI_FIXED } from './constants'; | ||
import { SKELETON_UI_PC, SKELETON_UI_TABLET, SKELETON_UI_MOBILE } from './constants'; | ||
|
||
import MoreButton from './components/MoreButton'; | ||
import MovieCard from './components/MovieCard'; | ||
import movieStore from './store/MovieStore'; | ||
import SearchBox from './components/SearchBox'; | ||
import searchMovieStore from './store/SearchMovieStore'; | ||
|
||
import SearchBox from './components/SearchBox'; | ||
import MovieCard from './components/MovieCard'; | ||
import Modal from './components/Modal'; | ||
|
||
import Logo from './images/logo.png'; | ||
|
||
type Tpage = 'popular' | 'search'; | ||
|
||
export default class App { | ||
#pageType: Tpage = 'popular'; | ||
|
||
#observer: IntersectionObserver | null = null; | ||
|
||
#isLoading: boolean = false; | ||
|
||
#skeletonBySize: number = SKELETON_UI_PC; | ||
|
||
async run() { | ||
this.#insertLogo(); | ||
this.#generateMovieList(); | ||
this.#generateSearchBox(); | ||
this.#addHomeButtonEvent(); | ||
this.#initEventListeners(); | ||
this.#setupIntersectionObserver(); | ||
} | ||
|
||
#insertLogo() { | ||
const homeButton = document.getElementById('home-button'); | ||
const imgElement = document.createElement('img'); | ||
|
||
imgElement.src = Logo; | ||
imgElement.alt = 'MovieList 로고'; | ||
|
||
homeButton?.appendChild(imgElement); | ||
} | ||
|
||
#getSkeletonCount() { | ||
const width = window.innerWidth; | ||
let skeletonCount = this.#skeletonBySize; | ||
|
||
if (width <= 390) { | ||
skeletonCount = SKELETON_UI_MOBILE; | ||
} else if (width <= 834) { | ||
skeletonCount = SKELETON_UI_TABLET; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 반응형을 위해 이런 조건이 추가된거 같은데 매직 넘버 대신 상수를 사용해보시면 좋을거 같아요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 매직 넘버 대신 상수 사용하기! |
||
|
||
return skeletonCount; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 로직을 |
||
|
||
#generateMovieList() { | ||
|
@@ -35,13 +70,19 @@ export default class App { | |
async #generateItemList(title: string, fetchData: () => Promise<Movie[]>, store: any) { | ||
this.#changeTitle(title); | ||
this.#removePreviousError(); | ||
|
||
const ulElement = document.querySelector('ul.item-list'); | ||
|
||
if (ulElement) { | ||
this.#generateSkeletonUI(ulElement as HTMLElement); | ||
const skeletonCount = this.#getSkeletonCount(); | ||
this.#generateSkeletonUI(ulElement as HTMLElement, skeletonCount); | ||
|
||
const newData = await fetchData(); | ||
|
||
this.#removeSkeletonUI(); | ||
this.#appendMovieCard(newData, ulElement as HTMLElement); | ||
|
||
this.#observeSentinel(); | ||
} | ||
} | ||
|
||
|
@@ -61,17 +102,12 @@ export default class App { | |
|
||
ulElement?.appendChild(card.element); | ||
}); | ||
|
||
this.#generateMoreButton(); | ||
} | ||
|
||
// eslint-disable-next-line max-lines-per-function | ||
#generateSkeletonUI(ulElement: HTMLElement) { | ||
this.#removeMoreButton(); | ||
|
||
#generateSkeletonUI(ulElement: HTMLElement, skeletonCount: number) { | ||
const fragment = new DocumentFragment(); | ||
|
||
for (let i = 0; i < SKELETON_UI_FIXED; i++) { | ||
for (let i = 0; i < skeletonCount; i++) { | ||
const card = new MovieCard({ | ||
classes: ['skeleton-container'], | ||
}); | ||
|
@@ -92,34 +128,49 @@ export default class App { | |
} | ||
} | ||
|
||
/* eslint-disable max-lines-per-function */ | ||
#generateMoreButton() { | ||
this.#removeMoreButton(); | ||
#setupIntersectionObserver() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 무한스크롤 역시 이번에 처음 구현해보았습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. API key가 없어 직접 코드를 구현해서 확인하지는 못 했네요 😅 |
||
const options = { | ||
root: null, | ||
rootMargin: '0px', | ||
threshold: 0.8, | ||
}; | ||
|
||
this.#observer = new IntersectionObserver(this.#handleIntersection, options); | ||
const sentinel = document.createElement('li'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기에서 만든 sentinel( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 맞네요,,ㅎ intersection obersever API에 대한이해가 덜 된 상태에서 하려다보니 놓쳤던 것 같습니다. |
||
sentinel.classList.add('sentinel'); | ||
this.#observer.observe(sentinel); | ||
} | ||
|
||
if (searchMovieStore.presentPage === searchMovieStore.totalPages) return; | ||
#observeSentinel() { | ||
const sentinel = document.querySelector('.sentinel'); | ||
if (sentinel && this.#observer) { | ||
this.#observer.observe(sentinel); | ||
} | ||
} | ||
|
||
const itemView = document.querySelector('section.item-view'); | ||
|
||
const moreBtn = new MoreButton({ | ||
onClick: () => { | ||
if (this.#pageType === 'popular') { | ||
movieStore.increasePageCount(); | ||
this.#generateMovieList(); | ||
} else { | ||
searchMovieStore.increasePageCount(); | ||
this.#generateSearchMovieList(); | ||
} | ||
}, | ||
#handleIntersection = (entries: IntersectionObserverEntry[]) => { | ||
entries.forEach((entry) => { | ||
if (entry.isIntersecting && entry.intersectionRatio >= 0.8 && !this.#isLoading) { | ||
this.#loadMoreMovies(); | ||
} | ||
}); | ||
}; | ||
|
||
itemView?.appendChild(moreBtn.element); | ||
} | ||
// eslint-disable-next-line max-lines-per-function | ||
async #loadMoreMovies() { | ||
if (searchMovieStore.presentPage === searchMovieStore.totalPages) return; | ||
|
||
#removeMoreButton() { | ||
const moreButton = document.getElementById('more-button'); | ||
if (moreButton) { | ||
moreButton.parentNode?.removeChild(moreButton); | ||
this.#isLoading = true; | ||
|
||
if (this.#pageType === 'popular') { | ||
await movieStore.increasePageCount(); | ||
await this.#generateMovieList(); | ||
} else { | ||
await searchMovieStore.increasePageCount(); | ||
await this.#generateSearchMovieList(); | ||
} | ||
|
||
this.#isLoading = false; | ||
} | ||
|
||
#removePreviousError() { | ||
|
@@ -130,6 +181,7 @@ export default class App { | |
} | ||
} | ||
|
||
// eslint-disable-next-line max-lines-per-function | ||
#generateSearchBox() { | ||
const header = document.querySelector('header'); | ||
const ulElement = document.querySelector('ul.item-list'); | ||
|
@@ -154,7 +206,6 @@ export default class App { | |
this.#pageType = 'popular'; | ||
this.#changeTitle('지금 인기 있는 영화'); | ||
this.#removePreviousError(); | ||
this.#removeMoreButton(); | ||
this.#renderAllMovieList(); | ||
}); | ||
} | ||
|
@@ -169,5 +220,24 @@ export default class App { | |
|
||
ulElement.innerHTML = ''; | ||
this.#appendMovieCard(movieDatas, ulElement as HTMLElement); | ||
this.#observeSentinel(); | ||
} | ||
|
||
#initEventListeners() { | ||
const itemList = document.querySelector('ul.item-list'); | ||
|
||
if (itemList) { | ||
itemList.addEventListener('click', this.#handleMovieCardClick.bind(this)); | ||
} | ||
} | ||
|
||
#handleMovieCardClick(event: any) { | ||
const clickedElement = event.target.closest('.item-card'); | ||
|
||
if (clickedElement) { | ||
const movieId = Number(clickedElement.dataset.movieid); | ||
const modal = Modal.getInstance(movieId); | ||
modal.openModal(); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이건 누구에게 전달하는 메세지인가용
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
완료된 기능은 체크해줘도 좋을거 같네요 ㅎㅎ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
급하게 제출하느라고 놓쳤네요,,ㅎ 리뷰 재요청하기 전에 정리하도록 하겠습니다.