노력이 좋아서

<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')})