feat(installer-rp): auto-start install with progress card grid
2단계 페이지 진입 즉시 설치를 시작하고, 음악·사진을 1번부터 카드 그리드로 한눈에 볼 수 있게 만든다. 다운로드는 % 게이지로, 완료/실패는 색상으로 표시. - main: prep/item/package phase 의 ProgressEvent 를 renderer 로 송신 - music.ts: yt-dlp stdout 의 [download] X% 라인을 파싱해 onProgress 호출 - preload: onProgress 채널 구독 함수 노출 - renderer: 다음 버튼 제거, prep chip + music/image 카드 그리드 + 빌드 상태 - styles: progressCard / prepChip / progressGrid 스타일 추가 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -104,52 +104,137 @@ function renderStep1() {
|
||||
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 =
|
||||
'<h2>2단계. 리소스팩 설치</h2>' +
|
||||
'<p class="formMessage">아래 "다음"을 누르면 음악·사진을 받아 리소스팩을 만들고 ' +
|
||||
'<code>%appdata%/.minecraft/resourcepacks/</code> 에 넣습니다.</p>' +
|
||||
'<p class="formMessage">음악·사진을 받아 리소스팩을 만들고 ' +
|
||||
'<code>%appdata%/.minecraft/resourcepacks/</code> 에 자동 설치합니다.</p>' +
|
||||
'<div class="prepRow">' +
|
||||
' <span class="prepChip" id="chip-ytdlp">yt-dlp 준비</span>' +
|
||||
' <span class="prepChip" id="chip-ffmpeg">ffmpeg 준비</span>' +
|
||||
'</div>' +
|
||||
'<div class="progressSection">' +
|
||||
' <h3>음악 다운로드</h3>' +
|
||||
' <div class="sectionSub" id="music-sub">' + musicTotal + '곡</div>' +
|
||||
' <div class="progressGrid" id="musicGrid"></div>' +
|
||||
'</div>' +
|
||||
'<div class="progressSection">' +
|
||||
' <h3>사진 다운로드</h3>' +
|
||||
' <div class="sectionSub" id="image-sub">' + imageTotal + '장</div>' +
|
||||
' <div class="progressGrid" id="imageGrid"></div>' +
|
||||
'</div>' +
|
||||
'<div class="progressSection">' +
|
||||
' <h3>리소스팩 빌드</h3>' +
|
||||
' <div class="sectionSub" id="pkg-sub">대기 중…</div>' +
|
||||
'</div>' +
|
||||
'<div class="actionRow">' +
|
||||
' <button class="secondaryBtn" id="prev">이전</button>' +
|
||||
' <button class="primaryBtn" id="start">다음</button>' +
|
||||
' <button class="secondaryBtn" id="cancel" hidden>취소</button>' +
|
||||
' <span></span>' +
|
||||
' <button class="dangerBtn" id="cancel">취소</button>' +
|
||||
'</div>'
|
||||
pageHost.appendChild(section)
|
||||
var prevBtn = section.querySelector('#prev')
|
||||
var startBtn = section.querySelector('#start')
|
||||
|
||||
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')
|
||||
|
||||
prevBtn.addEventListener('click', function () {
|
||||
if (state.installing) return
|
||||
renderStep1()
|
||||
})
|
||||
function buildCard(idx) {
|
||||
var card = document.createElement('div')
|
||||
card.className = 'progressCard pending'
|
||||
card.setAttribute('data-idx', String(idx))
|
||||
card.innerHTML =
|
||||
'<div class="cardTop"><span class="label">' + idx + '</span><span class="icon">○</span></div>' +
|
||||
'<div class="bar"><span></span></div>' +
|
||||
'<div class="pct">대기</div>'
|
||||
return card
|
||||
}
|
||||
for (var m = 1; m <= musicTotal; m++) musicGrid.appendChild(buildCard(m))
|
||||
for (var k = 1; k <= imageTotal; k++) imageGrid.appendChild(buildCard(k))
|
||||
|
||||
startBtn.addEventListener('click', function () {
|
||||
if (state.installing) return
|
||||
state.installing = true
|
||||
startBtn.disabled = true
|
||||
prevBtn.disabled = true
|
||||
cancelBtn.hidden = false
|
||||
logViewer.hidden = false
|
||||
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 = '○'
|
||||
}
|
||||
}
|
||||
|
||||
api.startInstall().then(function (result) {
|
||||
state.installing = false
|
||||
state.installed = true
|
||||
state.resourcepackPath = (result && result.resourcepackPath) || ''
|
||||
renderStep3()
|
||||
}).catch(function (err) {
|
||||
state.installing = false
|
||||
startBtn.disabled = false
|
||||
prevBtn.disabled = false
|
||||
cancelBtn.hidden = true
|
||||
alert('설치 실패: ' + (err.message || err))
|
||||
})
|
||||
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단계: 완료 ────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user