05-16 05:32
Recent Posts
Recent Comments
관리 메뉴

miinsun

[2022 웨일대학] 1차 미션 웨일 확장앱 만들기_2 (코드 리뷰) 본문

Extracurricular Activies/2022 웨일대학

[2022 웨일대학] 1차 미션 웨일 확장앱 만들기_2 (코드 리뷰)

miinsun 2022. 9. 25. 22:55

'스터디 온' 웨일 확장앱을 개발하며 새로 배운 기술들과 주요 코드들에 대한 리뷰를 정리하는 게시글! 코드에 대한 자세한 설명은 주석을 참고하면 된다.

1. localStorage를 이용해 데이터를 저장/활용

localStorage는 브라우저 세션 간에 저장된 데이터를 서로 공유한다. sessionStorage와 비슷하지만, localStorage는 페이지가 닫혀도 만료되지 않는다. localStorage의 이런 특징을 이용해서, 하루치 목표 달성률과 한달치 목표 달성률을 다음과 같이 저장했다. 당일 날짜를 저장해서 해당날짜가 지나면 하루치 목표 달성률이 초기화되도록 지정했다. (localStorage는 개발자 모드의 Application탭에서 확인 가능)

 

localStorage의 데이터를 사용하기 위해서 json으로 한번 파싱해줬다.

// localStorage 데이터 가져오기
var studyOn = JSON.parse(localStorage.getItem("study-on"));

// today 초기화
studyOn.today.push(
{
    study: 0,
    rest: 0,
    goal: subject.goal,
    name: subject.name
}
)

// localStorage 데이터 저장
localStorage.setItem("study-on", JSON.stringify(studyOn));

 

2. 카운트 시작/종료 (타이머 만들기)

공부를 진행하는 도중에는 타이머가 목표 시간까지 진행되고, 원이 조금씩 채워진다. 타이머는 javaScript의 setInterval()을 이용해 1초에 한번씩 작업을 진행하도록 했다. 뒤의 원이 진행시간/목표시간 비율에 맞게 채워지도록 계산을 매초 계산을 해줬다. 타이머가 목표시간에 도달하면 타이머를 종료하고, noti를 보내준다.

function startTimer(sub) {
  cnt = sub.study;
  timer = setInterval(function () {
    document.getElementById("sub").innerHTML = printTime(cnt++);

    // 원 크기 키우기
    var c1 = document.getElementById('circle_progress');
    var c1Size = (cnt / sub.goal) * 158;	// (현재 진행 시간 / 목표 시간)
    c1.style.strokeDasharray = c1Size + " 158";

    // 성공적으로 마쳤을 시, 
    if (cnt > sub.goal) {
      // 버튼 숨기기
      document.getElementById('stop-btn').style.display = "none";
      document.getElementById('rest-btn').style.display = "none";
      document.getElementById('end-btn').style.display = "flex";

      // notification 보내기
      notify();

	  // 타이머 종료
 	  clearInterval(timer);
    }
  }, 1000);
}

 

3. 달력 (datePicker)

date를 선택할 수 있는 달력을 만들고, 달력의 날짜를 누르면 이벤트가 발생하도록 했다. 달력의 요일을 클릭하면 해당일의 진행했던 공부시간과 휴식시간을 볼 수 있다. 

function showCalendar(month, year) {
    var firstDay = new Date(year, month).getDay();
    var monthString = monthsArr[month];

    table = document.getElementById("calendar-body");
    table.innerHTML = "";
    monthHeader.innerHTML = year + ". " + monthString;

    var date = 1;

    for (var i = 0; i < 6; i++) {
      var row = document.createElement("tr");

      for (var j = 0; j < 7; j++) {
        if (i === 0 && j < firstDay) {
          cell = document.createElement("td");
          cellText = document.createTextNode("");
          cell.appendChild(cellText);
          row.appendChild(cell);
        } else if (date > daysInMonth(month, year)) {
          break;
        } else {
          cell = document.createElement("td");
          cell.setAttribute("id", "day-"+date);
          cell.setAttribute("data-date", date);
          cell.setAttribute("data-month", month + 1);
          cell.setAttribute("data-year", year);
          cell.setAttribute("data-day", j);
          cell.setAttribute("data-month-name", months[month]);
          cell.className = "date-picker";
          if (date > 10) {
            cell.innerHTML = "<span>" + date + "</span>";
          }
          else {
            cell.innerHTML = "<span>&nbsp;" + date + "&nbsp;</span>";
          }

          // 날짜 클릭 시
          cell.onclick = function (event) {
            picked.style.display = "none";
            var dates = document.querySelectorAll(".date-picker");
            var currentTarget = event.currentTarget;
            var date = currentTarget.dataset.date.padStart(2, '0');
            var month = currentTarget.dataset.month - 1;
            var day = parseInt(currentTarget.dataset.day);

            for (var i = 0; i < dates.length; i++) {
              dates[i].classList.remove("selected");
            }
            currentTarget.classList.add("selected");

            // date 클릭 시, 출력
            datePicked.innerHTML = monthsArr[month] + "." + date + " " + "(" + getTodayLabel(day) + ")";
            var pickedDay = studyOn.calendar.find(x => x.day == date);

            // 공부 시작이 없을 시
            if (pickedDay == null) {
              document.getElementById('picked-null').style.display="block";
            }
            else {
              picked.style.display = "block";
              document.getElementById('picked-null').style.display="none";
              document.getElementById('study-time').innerHTML = printTime(pickedDay.study);
              document.getElementById('rest-time').innerHTML = printTime(pickedDay.rest);
            }
          }

          if (date === today.getDate() && year === today.getFullYear() && month === today.getMonth()) {
            cell.className = "date-picker selected";
          }

          row.appendChild(cell);
          date++;
        }
      }

      table.appendChild(row);
    }
  }
  
  function daysInMonth(month, year) {
    return 32 - new Date(year, month, 32).getDate();
  }

 

 

4. 목표 달성률 시각화(도넛 그래프, 프로그레스바)

도넛 그래프 같은 경우는 공부 시간과 쉬는 시간의 합이 158이 되도록 비율을 만들어줬다. 공부 시간 / (공부 시간 + 쉬는 시간) 이런 식으로 비율을 구하면 된다. 만약 요소가 3개라면 3개의 총합에 대한 비율을 구해주면 된다. 프로그레스 바는 목표시간대비 공부 달성률을 표현한다. 프로그레스 바는 원이 아니기 때문에 100%에 대한 비율을 구해주면 된다. 

<div id="display-wrapper">
    <div id="label1"></div>
    <div id="lable1-name">열공중</div>
    <div id="label2"></div>
    <div id="lable2-name">쉬는중</div>

    <!-- 도넛 그래프 -->
    <svg id="circle-base" viewBox="0 0 100 100">
        <circle id="c2" r="25" cx="50" cy="50" />
        <circle id="c1" r="25" cx="50" cy="50" />
        <text id="text" x="50" y="50" text-anchor="middle">

        </text>
        <div id="circle-img"></div>
        <div id="text2">00:50:00</div>
    </svg>
    
    <!-- 프로그레스 바 -->
    <div id="progressbar-wrapper">
        <div class="progress progress-striped">
            <div id="progressBar" class="progress-bar progress-bar-danger" role="progressbar"
                aria-valuenow="80" aria-valuemin="0" aria-valuemax="100" style="width: 40%">
            </div>
        </div>
        <div id="percent"></div>
        <div id="text3">달성률</div>
    </div>
</div>
// 원 비율 변경하기
// 공부비율
var c1 = document.getElementById('c1');
var c1Size = (totalStudy / (totalStudy + totalRest)) * 158;
c1.style.strokeDasharray = c1Size + " 158";

// 쉬는 시간 비율
var c2 = document.getElementById('c2');
var c2Size = c1Size + (totalRest / (totalStudy + totalRest)) * 158;
c2.style.strokeDasharray = c2Size + " 158";

// 프로그레스바 설정
var progressBar = document.getElementById('progressBar');
progressBar.style.width = (totalStudy / totalGoal) * 100 + "%";
progressBar.max = totalGoal;

// 프로그레스바 아이콘 설정
var per = document.getElementById('percent');
per.innerHTML = parseInt(totalStudy / totalGoal * 100) + "%";

 

5. 화면 스크린샷 저장(html2canvas)

확장 앱 같은 경우 외부 라이브러리를 자유롭게 사용하기 어렵기 때문에 html2canvas.min.js를 프로젝트에 추가해 다음과 같이 사용해줬다. document.write로 라이브러리를 import하고, 캡쳐된 이미지 파일에는 오늘의 날짜 + 'studyon'으로 이름을 지정해줬다.

document.write('<script src="../js/html2canvas.min.js"></script>');

function download() {
  var downBtn = document.getElementById('download-btn');
  downBtn.onclick = function () {
    html2canvas(document.body).then(canvas => {
      //다운로드 되는 이미지 파일 이름 지정
      saveAs(canvas.toDataURL('image/jpg'), studyOn.date.year + "_" +
        studyOn.date.month + "_" + studyOn.date.day + "_studyon.jpg"); 
    });
  };
}

function saveAs(uri, filename) {
  // 캡처된 파일을 이미지 파일로 내보냄
  var link = document.createElement('a');
  if (typeof link.download === 'string') {
    link.href = uri;
    link.download = filename;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  } else {
    window.open(uri);
  }
}

html2canvas는 아래에서 다운로드 가능하다.

 

html2canvas - Screenshots with JavaScript

Try out html2canvas Test out html2canvas by rendering the viewport from the current page. Capture

html2canvas.hertzen.com

 

6. 확장 앱 Notification API

위와 같이 생긴 알림 창을 notification이라고 하는데, 이번 프로젝트에서 목표 시간에 도달하면 알림이 뜨도록 notificationAPI를 이용했다. 먼저 manifest.json에서 notification의 권한을 허용해주고 background 프로세스를 지정해준다.

// background.js
whale.runtime.onMessage.addListener(data => {
  if (data.type === 'notification') {
    chrome.notifications.create('', data.options);
  }
});

// notification 보내기
function notify() {
  // 권한이 없으면 권한을 먼저 받는다.
  if (Notification.permission !== 'granted')
    Notification.requestPermission();
  else {
    var notification = new Notification('StudyOn', {
      icon: '../../images/icon_day.png',
      body: '목표 시간까지 공부 성공!',
    });

    // 알림 클릭 시, 사이드바로 이동
    notification.onclick = function () {
      whale.sidebarAction.show();
    };
  }
}

background와 notificationAPI 관한 자세한 내용은 아래 공식 문서에서 참고가능하다.

 

기본 구조와 용어

확장앱의 유일한 필수 파일은 manifest.json 뿐이며 매니페스트라고 부릅니다. 폴더 혹은 압축 파일 내에 형식에 맞는 매니페스트 파일 하나만 있어도 설치하거나 웨일 스토어에 등록을 신청할 수

developers.whale.naver.com

 

chrome.notifications - Chrome Developers

Build the next generation of web experiences.

developer.chrome.com

 

완성된 어플리케이션은, 아래 링크에서 다운로드 가능합니다~

https://store.whale.naver.com/detail/ajepkpjldcbakhoficmpmhpfgcblcioj

 

StudyOn

웨일대학 2팀 - 대학생 학습 관리 확장 앱.

store.whale.naver.com

 

Comments