노력이 좋아서
<step109>'코테연습_고양이 사진첩 만들기'
zoaseo
2022. 8. 26. 10:51
components/BreadCrumb.js
// BreadCrumb.js
export default function BreadCrumb({
$target,
initialState
}){
this.state = initialState;
this.$element = document.createElement('nav');
this.$element.className = 'BreadCrumb';
$target.appendChild(this.$element);
this.setState = (nextState) => {
this.state = nextState;
this.render();
}
this.render = () => {
this.$element.innerHTML = `
<div class='nav-item'>root</div>
${this.state.map((node, index)=>`<div class='nav-item' data-index="${index}">${node.name}</div>`).join('')}
`
}
}
components/ImageView.js
const IMG_PATH = 'https://fe-dev-matching-2021-03-serverlessdeploymentbuck-t3kpj3way537.s3.ap-northeast-2.amazonaws.com/public'
export default function ImageView({
$target,
initialState
}){
this.state = initialState;
this.$element = document.createElement('div');
this.$element.className = 'Modal ImageViewer';
$target.appendChild(this.$element);
// 상태변경
this.setState = (nextState) => {
this.state = nextState;
this.render();
}
this.render = () => {
this.$element.innerHTML = `
<div class="content">${this.state ?
`<img src="${IMG_PATH}${this.state}">`
: ''}
</div>
`;
this.$element.style.display = this.state ? 'block' : 'none';
}
this.render();
}
components/Nodes.js
export default function Nodes({$target, initialState, onClick, onBackClick}){
// div태그 생성
this.$element = document.createElement('div');
// 클래스 지정
this.$element.className = 'Nodes';
// main태그의 마지막 자식요소로 추가
$target.appendChild(this.$element);
this.state = initialState;
this.onClick = onClick;
this.onBackClick = onBackClick;
// state변경 함수
this.setState = (nextState) => {
this.state = nextState
this.render();
}
this.render = () => {
if(this.state.imgLists.length > 0) {
const listsTemplate = this.state.imgLists.map(list=> {
const imgpath = list.type === 'FILE' ? './assets/file.png' : './assets/directory.png'
return `
<div class="Node" data-index="${list.id}">
<img src="${imgpath}">
<div>${list.name}</div>
</div>
`
}).join('')
// 내 필드 $element의 내용으로 넣어줌
// root일 때는 뒤로가기 항목 + listTemplate 아닐 때는 listTemplate
this.$element.innerHTML = !this.state.isRoot ? ` <div class="Node">
<img src="./assets/prev.png">
</div>${listsTemplate}` : listsTemplate
}
// 렌더링된 이후 클릭 가능한 모든 요소에 click 이벤트 설정
this.$element.querySelectorAll('.Node').forEach($node => {
$node.addEventListener('click', (e) => {
// dataset객체 안에 index값 할당
const { index } = e.target.closest('div').dataset;
if(!index){
this.onBackClick();
}
// imgLists배열에서 선택한(클릭한) 노드를 찾아서 selectedNode에 할당
const selectedNode = this.state.imgLists.find(list=>list.id === index);
if(selectedNode) {
this.onClick(selectedNode)
}
})
})
// this.$element.addEventListener('click',(e)=>{
// // dataset객체 안에 index값 할당
// const { index } = e.target.closest('div').dataset;
// console.log(index);
// // imgLists배열에서 선택한(클릭한) 노드를 찾아서 selectedNode에 할당
// const selectedNode = this.state.imgLists.find(list=>list.id === index);
// if(selectedNode) {
// this.onClick(selectedNode)
// }
// })
}
}
api.js
const API_URL = 'https://zl3m4qq0l9.execute-api.ap-northeast-2.amazonaws.com/dev';
export const request = async(id) => {
try{
const res = await fetch(`${API_URL}/${id ? id : ''}`)
if(res.ok){
const jsonData = await res.json()
return jsonData;
} else {
throw new Error(`전송에 오류가 생겼습니다.`)
}
}
catch(e){
throw new Error(`전송에 오류가 생겼습니다. ${e.message}`)
}
}
App.js
import { request } from "./api.js";
import BreadCrumb from "./src/styles/components/BreadCrumb.js";
import ImageView from "./src/styles/components/ImageView.js";
import Nodes from "./src/styles/components/Nodes.js";
export default function App({$target}){
this.state = {
// 루트인지, 아닌지 구분하는 속성
isRoot: true,
// 조회한 배열
imgLists: [],
// 단계 배열
depth: [],
// 선택한 이미지 경로
selectedUrl: ""
}
this.setState = (nextState) => {
// app 상태 업데이트
this.state = nextState;
nodes.setState({
isRoot: this.state.isRoot,
imgLists: this.state.imgLists
})
// breadcrumb객체 상태 변경
// this.state.depth 배열 전달
breadcrumb.setState(this.state.depth)
// imageView객체 상태 변경
imageview.setState(this.state.selectedUrl)
}
// breadcrumb객체 생성
const breadcrumb = new BreadCrumb({
$target,
initialState: this.state.depth
})
// nodes객체 생성
const nodes = new Nodes({
$target,
initialState: [],
onClick: async (node) => {
try{
// 노드타입이 'DIRECTORY'라면
if(node.type === 'DIRECTORY'){
const nextNodes = await request(node.id)
this.setState({
...this.state,
// depth배열에 node추가하기
depth: [...this.state.depth, node],
imgLists: nextNodes,
isRoot: false
})
}
// 노드타입이 'FILE'이라면
else if(node.type === 'FILE'){
// selectedUrl 값 변경 업데이트
this.setState({
...this.state,
selectedUrl: node.filePath
})
}
}
catch(e){
// console.log(this.state.imgLists)
}
},
onBackClick: async () => {
try{
// 이전 state 복사본 만들기
const nextState = {...this.state}
// depth배열의 마지막 요소 제거
nextState.depth.pop();
// depth배열의 길이가 0이면 null을 할당하고 그렇지 않으면
// depth배열의 마지막 요소의 id를 할당
const prevNodeId = nextState.depth.length === 0 ? null :
nextState.depth[nextState.depth.length - 1].id
// prevNodeId === null 이면 root처리
if(prevNodeId === null) {
// 전체 데이터 요청
const rootNodes = await request()
this.setState({
...nextState,
isRoot: true,
imgLists: rootNodes
})
}
// 아닐 때는 이전id값으로 데이터 요청
else {
const rootNodes = await request(prevNodeId)
this.setState({
...nextState,
isRoot: false,
imgLists: rootNodes
})
}
}
catch(e) {
}
}
})
// imageview객체 생성
const imageview = new ImageView({
$target,
initialState: this.state.selectedUrl
})
// app 호출시 데이터 불러오기
const init = async () => {
try {
// fetch 요청해서 초기데이터 받아오기
const rootNodes = await request()
this.setState({
...this.state,
// 초기데이터이므로 isRoot는 true
// imgLists 배열에 받아온 배열을 할당
isRoot: true,
imgLists: rootNodes
})
console.log(this.state.depth)
}
catch(e) {
// 에러처리
}
}
// 초기화
init();
}
index.html
<html>
<head>
<title>고양이 사진첩!</title>
<link rel="stylesheet" href="./src/styles/style.css">
</head>
<body>
<h1>고양이 사진첩</h1>
<main class="App">
<!-- <nav class="Breadcrumb">
<div>root</div>
<div>노란고양이</div>
</nav> -->
<!-- <div class="Nodes">
<div class="Node">
<img src="./assets/prev.png">
</div>
<div class="Node">
<img src="./assets/directory.png">
<div>2021/04</div>
</div>
<div class="Node">
<img src="./assets/file.png">
<div>하품하는 사진</div>
</div>
</div> -->
</main>
<script src="./index.js" type="module"></script>
<!-- Loading Layer sample markup -->
<!--
<div class="Modal Loading">
<div class="content">
<img src="./assets/nyan-cat.gif">
</div>
</div>
<!-- ImageViewer sample markup -->
<!--
<div class="Modal ImageViewer">
<div class="content">
<img src="./assets/sample_image.jpg">
</div>
-->
</body>
</html>
index.js
import App from './App.js';
new App({$target: document.querySelector('.App')})