'use strict' const api = window.rpInstaller const state = { packs: [], selectedKey: null, installing: false, installed: false, resourcepackPath: '' } const pageHost = document.getElementById('pageHost') const stepIndicator = document.getElementById('stepIndicator') const logViewer = document.getElementById('logViewer') const logBody = document.getElementById('logBody') const logToggle = document.getElementById('logToggle') logToggle.addEventListener('click', function () { logViewer.classList.toggle('collapsed') if (logViewer.classList.contains('collapsed')) { logViewer.style.height = '36px' logToggle.textContent = '펼치기' } else { logViewer.style.height = '' logToggle.textContent = '접기' } }) api.onLog(function (line) { logViewer.hidden = false logBody.textContent += line + '\n' logBody.scrollTop = logBody.scrollHeight }) function setActiveStep(step) { stepIndicator.querySelectorAll('li').forEach(function (item) { var index = Number(item.getAttribute('data-step')) item.classList.remove('active', 'done') if (index < step) item.classList.add('done') if (index === step) item.classList.add('active') }) } function clearPage() { pageHost.innerHTML = '' } // ── 1단계: 음악퀴즈 선택 ──────────────────────────── function renderStep1() { setActiveStep(1) clearPage() var section = document.createElement('section') section.className = 'page' section.innerHTML = '

1단계. 음악퀴즈 선택

' + '

목록을 불러오는 중...

' + '
' pageHost.appendChild(section) var listEl = section.querySelector('#packList') var nextBtn = section.querySelector('#next') function renderList() { listEl.innerHTML = '' if (state.packs.length === 0) { listEl.innerHTML = '

등록된 음악퀴즈가 없습니다.

' return } state.packs.forEach(function (pack) { var card = document.createElement('button') card.type = 'button' card.className = 'choiceCard' if (state.selectedKey === pack.key) card.classList.add('active') var verLabel = pack.mcVersion ? '마인크래프트 ' + escapeHtml(pack.mcVersion) + ' · ' : '' card.innerHTML = '' + escapeHtml(pack.name) + '' + '' + verLabel + '음악 ' + pack.list.music.length + '곡 · 사진 ' + pack.list.images.length + '장' card.addEventListener('click', function () { state.selectedKey = pack.key nextBtn.disabled = false renderList() }) listEl.appendChild(card) }) } nextBtn.addEventListener('click', function () { if (!state.selectedKey) return api.selectPack(state.selectedKey).then(function () { renderStep2() }).catch(function (err) { alert(err.message || '선택 실패') }) }) api.loadPacks().then(function (packs) { state.packs = packs || [] renderList() }).catch(function (err) { listEl.innerHTML = '

목록 로드 실패: ' + escapeHtml(err.message || '') + '

' }) } // ── 2단계: 설치 진행 ──────────────────────────────── function renderStep2() { setActiveStep(2) clearPage() var pack = null for (var i = 0; i < state.packs.length; i++) { if (state.packs[i].key === state.selectedKey) { pack = state.packs[i]; break } } var musicTotal = pack ? pack.list.music.length : 0 var imageTotal = pack ? pack.list.images.length : 0 var section = document.createElement('section') section.className = 'page' section.innerHTML = '

2단계. 리소스팩 설치

' + '

음악·사진을 받아 리소스팩을 만들고 ' + '%appdata%/.minecraft/resourcepacks/ 에 자동 설치합니다.

' + '
' + ' yt-dlp 준비' + ' ffmpeg 준비' + '
' + '
' + '

음악 다운로드

' + '
' + musicTotal + '곡
' + '
' + '
' + '
' + '

사진 다운로드

' + '
' + imageTotal + '장
' + '
' + '
' + '
' + '

리소스팩 빌드

' + '
대기 중…
' + '
' + '
' + ' ' + ' ' + '
' pageHost.appendChild(section) var musicGrid = section.querySelector('#musicGrid') var imageGrid = section.querySelector('#imageGrid') var chipYtdlp = section.querySelector('#chip-ytdlp') var chipFfmpeg = section.querySelector('#chip-ffmpeg') var pkgSub = section.querySelector('#pkg-sub') var cancelBtn = section.querySelector('#cancel') function buildCard(idx) { var card = document.createElement('div') card.className = 'progressCard pending' card.setAttribute('data-idx', String(idx)) card.innerHTML = '
' + idx + '
' + '
' + '
대기
' return card } for (var m = 1; m <= musicTotal; m++) musicGrid.appendChild(buildCard(m)) for (var k = 1; k <= imageTotal; k++) imageGrid.appendChild(buildCard(k)) function updateCard(grid, index, percent, status) { var card = grid.querySelector('[data-idx="' + index + '"]') if (!card) return card.classList.remove('pending', 'running', 'done', 'error') card.classList.add(status) var bar = card.querySelector('.bar > span') if (bar) bar.style.width = Math.max(0, Math.min(100, percent)) + '%' var pct = card.querySelector('.pct') var icon = card.querySelector('.icon') if (status === 'done') { if (pct) pct.textContent = '완료' if (icon) icon.textContent = '✓' if (bar) bar.style.width = '100%' } else if (status === 'error') { if (pct) pct.textContent = '실패' if (icon) icon.textContent = '✕' } else if (status === 'running') { if (pct) pct.textContent = Math.round(percent) + '%' if (icon) icon.textContent = '⏳' } else { if (pct) pct.textContent = '대기' if (icon) icon.textContent = '○' } } var stopProgress = api.onProgress(function (payload) { if (!payload || typeof payload !== 'object') return if (payload.phase === 'prep') { if (payload.done) { chipYtdlp.classList.remove('active'); chipYtdlp.classList.add('done') chipFfmpeg.classList.remove('active'); chipFfmpeg.classList.add('done') return } if (payload.message && payload.message.indexOf('yt-dlp') >= 0) { chipYtdlp.classList.add('active') } else if (payload.message && payload.message.indexOf('ffmpeg') >= 0) { chipYtdlp.classList.remove('active'); chipYtdlp.classList.add('done') chipFfmpeg.classList.add('active') } return } if (payload.phase === 'item') { var grid = payload.kind === 'music' ? musicGrid : imageGrid updateCard(grid, payload.index, payload.percent || 0, payload.status) return } if (payload.phase === 'package') { pkgSub.textContent = payload.done ? '설치 완료' : (payload.message || '빌드 중…') return } }) cancelBtn.addEventListener('click', function () { if (!state.installing) return cancelBtn.disabled = true api.cancelInstall() }) // 페이지 진입 즉시 설치 시작 state.installing = true logViewer.hidden = false api.startInstall().then(function (result) { state.installing = false state.installed = true state.resourcepackPath = (result && result.resourcepackPath) || '' if (stopProgress) stopProgress() renderStep3() }).catch(function (err) { state.installing = false if (stopProgress) stopProgress() alert('설치 실패: ' + ((err && err.message) || err)) renderStep1() }) } // ── 3단계: 완료 ──────────────────────────────────── function renderStep3() { setActiveStep(3) clearPage() var section = document.createElement('section') section.className = 'page' section.innerHTML = '

3단계. 완료

' + '

리소스팩 설치를 완료했습니다.

' + (state.resourcepackPath ? '

' + escapeHtml(state.resourcepackPath) + '

' : '') + '
' + ' ' + ' ' + '
' pageHost.appendChild(section) section.querySelector('#openFolder').addEventListener('click', function () { api.openResourcepackFolder() }) section.querySelector('#finish').addEventListener('click', function () { api.quit() }) } function escapeHtml(s) { return String(s).replace(/[&<>"']/g, function (c) { return c === '&' ? '&' : c === '<' ? '<' : c === '>' ? '>' : c === '"' ? '"' : ''' }) } renderStep1()