[실전 프로젝트] 다이어리 페이지 마무리, 네비게이션 바 만들기
본문 바로가기
항해 중/8-13주차 실전 프로젝트

[실전 프로젝트] 다이어리 페이지 마무리, 네비게이션 바 만들기

by 은돌1113 2021. 12. 29.

오늘 한 일

1. 오후 1,2시까지 다이어리 페이지에 오류 없는 지 테스트 해보고, 변수/함수 정리할 예정

2. 네비게이션 바 컴포넌트 만들 예정

3. 힐링 보이스 페이지 구현

4. 로그인 유도 페이지 구현 하신 거 반영은 안했지만 프로젝트에 연결 해본 코드

예 → 로그인 페이지 / 아니오 or layout → 모달창 이전 페이지

더보기
더보기
import { fontWeight, margin, textAlign } from "@mui/system";
import React from "react";
import Modal from "react-modal";
import { history } from "../redux/configureStore";
const RequireLogin = (props) => {
  const [modal, setModal] = React.useState(true); // 모달창

  const yes = () => {
    history.push("/login");
  };

  const no = () => {
    setModal(!modal);
    history.push("/");
  };

  const modalOff = () => {
    setModal(!modal);
    history.goBack();
  };
  return (
    <>
      <Modal
        isOpen={modal}
        ariaHideApp={false}
        onRequestClose={modalOff}
        style={{
          overlay: {
            position: "fixed",
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            backgroundColor: "rgba(15, 15, 15, 0.79)",
          },
          content: {
            position: "absolute",
            top: "0%",
            left: "0%",
            width: "300px",
            height: "300px",
            border: "1px solid #ccc",
            background: "#C4C4C4ff",
            overflow: "auto",
            WebkitOverflowScrolling: "touch",
            borderRadius: "30px",
            outline: "none",
            padding: "0px",
            margin: "auto",
          },
        }}
      >
        <div style={{ height: "80px", lineHeight: "80px" }}>
          <p
            style={{
              fontSize: "13px",
              fontWeight: "700",
              textAlign: "center",
              margin: "40px 0px 0px 0px",
            }}
          >
            해당 서비스는 로그인 후 이용 가능합니다
          </p>
          <p
            style={{
              fontSize: "13px",
              fontWeight: "500",
              textAlign: "center",
              margin: "10px 0px 0px 0px",
            }}
          >
            로그인 하러 가시겠습니까?
          </p>
        </div>
        <div
          style={{
            display: "flex",
            alignContent: "flex-end",
            position: "absolute",
            bottom: "0px",
            width: "100%",
          }}
        >
          <button
            style={{
              width: "100%",
              height: "50px",
              border: "none",
              margin: "0px 1px 0px 0px",
            }}
            onClick={yes}
          >
            예
          </button>
          <button
            style={{ width: "100%", height: "50px", border: "none" }}
            onClick={no}
          >
            아니오
          </button>
        </div>
      </Modal>
    </>
  );
};

export default RequireLogin;

 

질문 사항

 

1) 음원 출력 할 때 음원이 재생 되어야 하는 건지 그렇다면 활성화도 같이 시켜야 하는 건지 재생 버튼이 있어야 하는 건지 → 볼륨 default 50%, 누르면 활성화 되면서 음성 들리고, 한번 더 누르면 비활성화 되면서 음성 꺼지도록 구현 해야 한다.

 

2) cookie 보다는 localStorage에 javascript 객체를 JSON 파일로 바꿔서 setItem 하고 getItem 할 때는 JSON 파일로 javascript 객체로 사용하거나 로그인 시 response로 받은 객체 데이터에서 필요한 데이터만 각각 setItem 하면 될 것 닽다. → 필요한 데이터(userIdx, token, noticeSet)만 각각 setItem 해서 localStorage에 담는 걸로 결정

 

기능 구현

 

1) 네비게이션 바 컴포넌트 구현

더보기
더보기
import React from "react";
import styled from "styled-components";
import { useHistory } from "react-router-dom";

const Navigation = (props) => {
  const history = useHistory();

  return (
    <>
      <div
        style={{
          display: "flex",
          justifyContent: "space-between",
          width: "20%",
          backgroundColor: "#dddddd",
          margin: "auto",
          marginTop: `${props.marginTop ? props.marginTop : ""}`,
          padding: "20px",
        }}
      >
        <div
          onClick={() => {
            history.push("/");
          }}
        >
          <Icon></Icon>홈
        </div>
        <div
          onClick={() => {
            history.push("/asmr");
          }}
        >
          <Icon></Icon>ASMR
        </div>
        <div
          onClick={() => {
            history.push("/diary");
          }}
        >
          <Icon></Icon>다이어리
        </div>
        <div
          onClick={() => {
            history.push("/mypage");
          }}
        >
          <Icon></Icon>마이
        </div>
      </div>
    </>
  );
};

const Icon = styled.div`
  width: 30px;
  height: 30px;
  border: 1px dotted black;
  margin: auto;
`;

export default Navigation;

 

2) 힐링 보이스 페이지 구현
: 전에 연습 했던 코드에서 업그레이드만 시켰다.

더보기
더보기
import React, { useState, useRef, useEffect } from "react";
import { useDispatch } from "react-redux";
import { actionCreators as voiceActions } from "../redux/modules/voice";

const AudioRecord = () => {
  // 오디오 녹음
  const [stream, setStream] = useState();
  const [media, setMedia] = useState();
  const [onRec, setOnRec] = useState(true);
  const [source, setSource] = useState();
  const [analyser, setAnalyser] = useState();
  const [audioUrl, setAudioUrl] = useState();
  const [sound, setSound] = useState();

  // 타이머
  const [count, setCount] = useState(0);
  const [currentMinutes, setCurrentMinutes] = useState(0);
  const [currentSeconds, setCurrentSeconds] = useState(0);
  const intervalRef = useRef(null);

  const [disabled, setDisabled] = useState(true);

  // --------------- 타이머 ---------------

  const start = () => {
    intervalRef.current = setInterval(async () => {
      setCount((c) => c + 1);
    }, 1000);
  };

  const end = () => {
    clearInterval(intervalRef.current);
  };

  const timer = () => {
    const checkMinutes = Math.floor(count / 60);
    const minutes = checkMinutes & 60;
    const seconds = count % 60;

    setCurrentMinutes(minutes);
    setCurrentSeconds(seconds);
  };

  // count의 변화에 따라 timer 함수 렌더링
  useEffect(timer, [count]);

  // --------------- 녹음 ---------------

  const onRecAudio = () => {
    setCount(0); // 재녹음 시 타이머 초기화
    start(); // 타이머 시작
    setDisabled(true);

    // 음원정보를 담은 노드를 생성하거나 음원을 실행또는 디코딩 시키는 일을 한다
    const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    // 자바스크립트를 통해 음원의 진행상태에 직접접근에 사용된다.
    const analyser = audioCtx.createScriptProcessor(0, 1, 1);
    setAnalyser(analyser);

    function makeSound(stream) {
      // 내 컴퓨터의 마이크나 다른 소스를 통해 발생한 오디오 스트림의 정보를 보여준다.
      const source = audioCtx.createMediaStreamSource(stream);
      setSource(source);
      source.connect(analyser);
      analyser.connect(audioCtx.destination);
    }
    // 마이크 사용 권한 획득
    navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
      const mediaRecorder = new MediaRecorder(stream);
      mediaRecorder.start();
      setStream(stream);
      setMedia(mediaRecorder);
      makeSound(stream);

      analyser.onaudioprocess = function (e) {
        // 3분(180초) 지나면 자동으로 음성 저장 및 녹음 중지
        if (e.playbackTime > 180) {
          stream.getAudioTracks().forEach(function (track) {
            track.stop();
          });
          mediaRecorder.stop();
          // 메서드가 호출 된 노드 연결 해제
          analyser.disconnect();
          audioCtx.createMediaStreamSource(stream).disconnect();

          mediaRecorder.ondataavailable = function (e) {
            setAudioUrl(e.data);
            setOnRec(true);
          };
        } else {
          setOnRec(false);
        }
      };
    });
  };

  // 사용자가 음성 녹음을 중지 했을 때
  const offRecAudio = () => {
    end();

    // dataavailable 이벤트로 Blob 데이터에 대한 응답을 받을 수 있음
    media.ondataavailable = function (e) {
      setAudioUrl(e.data);
      setOnRec(true);
    };

    // 모든 트랙에서 stop()을 호출해 오디오 스트림을 정지
    stream.getAudioTracks().forEach(function (track) {
      track.stop();
    });

    // 미디어 캡처 중지
    media.stop();

    // 메서드가 호출 된 노드 연결 해제
    analyser.disconnect();
    source.disconnect();

    if (audioUrl) {
      URL.createObjectURL(audioUrl); // 출력된 링크에서 녹음된 오디오 확인 가능
    }

    // File 생성자를 사용해 파일로 변환
    const sound = new File([audioUrl], "soundBlob", {
      lastModified: new Date().getTime(),
      type: "audio",
    });

    setDisabled(false);
    setSound(sound); // File 정보 출력
  };

  const play = () => {
    const audio = new Audio(URL.createObjectURL(audioUrl));
    audio.loop = false;
    audio.volume = 1;
    audio.play();
  };

  const dispatch = useDispatch();

  const send = () => {
    setCount(0);
    dispatch(voiceActions.voiceRecordDB(sound));
  };

  return (
    <>
      <h1>
        {currentMinutes < 10 ? `0${currentMinutes}` : currentMinutes} :{" "}
        {currentSeconds < 10 ? `0${currentSeconds}` : currentSeconds}
      </h1>
      <div>
        <button onClick={play} disabled={disabled}>
          재생
        </button>
        <button onClick={onRec ? onRecAudio : offRecAudio}>녹음</button>
        <button onClick={send} disabled={disabled}>
          전송
        </button>
      </div>
    </>
  );
};

export default AudioRecord;

 

import React from "react";
import { useSelector } from "react-redux";

const Bubble = (props) => {
  const style = { area: [], gap: [] }; // div들을 담고 있는 객체
  let checkCircle = ""; // 현재 재생 중인 음성의 index가 무엇인 지 담는다.
  let checkLineHeight = ""; // 현재 재생 중인 음성의 height 값을 담는다.
  const voiceInfo = useSelector((state) => state.voice.voiceInfo);

  for (let i = 0; i < 10; i++) {
    style.area.push(Math.ceil(Math.random() * (100 - 40) + 40)); // 동그라미
    style.gap.push(Math.ceil(Math.random() * (10 - 5) + 5)); // 간격
  }

  // ** 사용자가 누른 div만 정해진 색상으로 활성화 **
  const changeColor3 = (index, voice, item) => {
    if (checkCircle === "") {
      // 전에 눌렀던 게 없을 때
      let x = document.getElementById(index);
      checkLineHeight = item;
      x.style.backgroundColor = "purple";
      checkCircle = x;
    } else if (parseInt(checkCircle.id) === index) {
      console.log("눌렀던 거 또 누름");
    } else {
      // 전에 눌렀던 음성이 있으면 다시 비활성화
      checkCircle.style.backgroundColor = "grey";
      document.getElementById(checkCircle.id + "item").innerHTML = "▶";
      let x = document.getElementById(index);
      checkCircle = x; // 바꿔치기
      checkLineHeight = item; // 바꿔치지
      checkCircle.style.backgroundColor = "purple"; // 정해진 색상으로만 활성화 되게
    }

    document.getElementById(index + "item").innerHTML = "■";
    play(voice);
  };

  const play = (voice) => {
    const audio = new Audio(URL.createObjectURL(voice));
    audio.loop = false;
    audio.volume = 1;
    audio.play();
  };

  return (
    <div>
      <button
        onClick={() => {
          window.location.reload();
        }}
      >
        새로고침
      </button>
      <div
        style={{
          width: "23%",
          flexWrap: "wrap",
          display: "flex",
          justifyContent: "center",
          alignItems: "center",
        }}
      >
        {style.area.map((item, index) => {
          return (
            <p
              key={item}
              id={index}
              style={{
                width: `${item}px`,
                height: `${item}px`,
                backgroundColor: "grey",
                borderRadius: "50%",
                marginRight: `${style.gap[index]}px`,
                marginBottom: `-${style.gap[index]}px`,
              }}
              onClick={() => {
                changeColor3(index, voiceInfo[index], item);
              }}
            >
              <div
                id={`${index}item`}
                style={{
                  width: `${item}px`,
                  height: `${item}px`,
                  lineHeight: `${item}px`,
                }}
              >
                ▶
              </div>
            </p>
          );
        })}
      </div>
    </div>
  );
};

export default Bubble;

댓글