feat: 최종 리소스팩(선택 설치) + 약관 표시대상 추가

- pack.finalResourcepackPath 추가(/file/resourcepacks/outputs/, "."=없음, 기본 ".").
  편집기에 "최종 리소스팩 (.zip)" 입력 필드 추가.
- 약관 표시대상에 showInFinalResourcepack("최종 리소스팩에 표시") 3번째 토글 추가
  (스키마/시드/마이그레이션/편집기 체크박스·배지·저장 라우트, index.json 노출).
- 간편설치기 완료 단계: 기존 "직접 적용" 경고 제거 → 최종 리소스팩이 "."이 아니면
  "리소스팩을 설치하시겠습니까?" 예/아니요. 아니요→완료, 예→최종 리소스팩 약관
  (showInFinalResourcepack) 동의→/file/resourcepacks/outputs/ 에서 다운로드→완료.
  "." 이면 이 페이지 자체를 건너뜀.
- 0.4.15→0.4.16.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 00:33:07 +09:00
parent f53e54493d
commit e5dcc47548
13 changed files with 247 additions and 25 deletions

View File

@@ -255,6 +255,50 @@ ipcMain.handle('terms:list', async (): Promise<{ ok: boolean; terms?: Array<{ ki
}
})
// 최종 리소스팩 다운로드용 약관 목록. index.json 에서 showInFinalResourcepack=true 만 추린다.
ipcMain.handle('terms:listFinal', async (): Promise<{ ok: boolean; terms?: Array<{ kind: string; label: string }>; message?: string }> => {
if (!state.selectedKey) return { ok: false, message: 'pack not selected' }
try {
const url = `${state.baseUrl}/manifest/terms/${encodeURIComponent(state.selectedKey)}/index.json`
const buf = await fetchBuffer(url)
const parsed = JSON.parse(buf.toString('utf8')) as { terms?: unknown }
const items = Array.isArray(parsed.terms) ? parsed.terms : []
const terms: Array<{ kind: string; label: string }> = []
for (const it of items) {
if (!it || typeof it !== 'object') continue
const entry = it as Record<string, unknown>
if (entry.showInFinalResourcepack !== true) continue
const kind = typeof entry.kind === 'string' ? entry.kind : ''
const label = typeof entry.label === 'string' ? entry.label : ''
if (!TERM_KIND_RE.test(kind) || label.length === 0) continue
terms.push({ kind, label })
}
return { ok: true, terms }
} catch (error) {
return { ok: false, message: (error as Error).message }
}
})
// 최종 리소스팩(zip)을 /file/resourcepacks/outputs/ 에서 받아 .mc_custom/resourcepacks/ 에 저장.
ipcMain.handle('finalResourcepack:install', async (): Promise<{ ok: boolean; path?: string; message?: string }> => {
const pack = state.selectedKey ? state.packs.get(state.selectedKey) : undefined
if (!pack) return { ok: false, message: t('errors.packNotFound2') }
const finalName = pack.pack.finalResourcepackPath
if (!finalName || finalName === '.') return { ok: false, message: 'no final resourcepack' }
try {
const cleaned = path.basename(finalName.replace(/^\/+/, ''))
const url = `${state.baseUrl}/file/resourcepacks/outputs/${encodeURIComponent(cleaned)}`
const destDir = path.join(getAppDataDir(), getMcCustomDirName(), 'resourcepacks')
const dest = path.join(destDir, cleaned)
sendLog(t('log.finalResourcepackDownload', { url }))
await downloadFile(url, dest)
sendLog(t('log.finalResourcepackSaved', { path: dest }))
return { ok: true, path: dest }
} catch (error) {
return { ok: false, message: (error as Error).message }
}
})
ipcMain.handle('packs:select', async (_event, packKey: string) => {
if (!state.packs.has(packKey)) {
throw new Error(t('errors.packNotFound'))