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:
@@ -47,6 +47,7 @@ export function defaultPackDefinition(name: string): PackDefinition {
|
||||
platform: { type: 'vanilla' },
|
||||
modsFolder: '',
|
||||
resourcepackPath: '',
|
||||
finalResourcepackPath: '.',
|
||||
outputPackName: '',
|
||||
serverMinRam: 2048,
|
||||
serverMaxRam: 4096,
|
||||
@@ -97,6 +98,13 @@ export function normalizeRecommendedJdk(input: unknown): number {
|
||||
return Number.isFinite(n) && n >= 8 && n <= 99 ? n : DEFAULT_JDK_MAJOR
|
||||
}
|
||||
|
||||
/** "최종 리소스팩" 값 보정. 빈 값/`.` 은 "없음"을 뜻하는 `.` 으로, 그 외는 zip 파일명으로. */
|
||||
export function normalizeFinalResourcepack(input: unknown): string {
|
||||
const raw = typeof input === 'string' ? input.trim() : ''
|
||||
if (raw === '' || raw === '.') return '.'
|
||||
return sanitizeZipFileName(raw)
|
||||
}
|
||||
|
||||
export function normalizePackDefinition(input: Partial<PackDefinition> & Record<string, unknown>): PackDefinition {
|
||||
const fallback = defaultPackDefinition(typeof input.name === 'string' ? input.name : 'new')
|
||||
const platform = (input.platform ?? {}) as Partial<PackDefinition['platform']>
|
||||
@@ -125,6 +133,7 @@ export function normalizePackDefinition(input: Partial<PackDefinition> & Record<
|
||||
},
|
||||
modsFolder: sanitizeFolderName(input.modsFolder),
|
||||
resourcepackPath: sanitizeZipFileName(input.resourcepackPath),
|
||||
finalResourcepackPath: normalizeFinalResourcepack(input.finalResourcepackPath),
|
||||
// 표시명은 사용자 입력을 보존(공백/마침표 trim 만). 파일명 안전 처리는 설치기 측에서.
|
||||
outputPackName: typeof input.outputPackName === 'string' ? input.outputPackName.trim() : '',
|
||||
serverMinRam: clampNumber(input.serverMinRam, fallback.serverMinRam),
|
||||
@@ -393,12 +402,13 @@ const DEFAULT_TERM_SEEDS: Array<{
|
||||
label: string
|
||||
showInInstaller: boolean
|
||||
showInInstallerRp: boolean
|
||||
showInFinalResourcepack: boolean
|
||||
}> = [
|
||||
{ kind: 'map', label: '맵 약관', showInInstaller: true, showInInstallerRp: false },
|
||||
{ kind: 'mod', label: '모드 약관', showInInstaller: true, showInInstallerRp: false },
|
||||
{ kind: 'installer', label: '설치기 약관', showInInstaller: true, showInInstallerRp: false },
|
||||
{ kind: 'resourcepack', label: '리소스팩 약관', showInInstaller: false, showInInstallerRp: true },
|
||||
{ kind: 'installer-rp', label: '리소스팩 설치기 약관', showInInstaller: false, showInInstallerRp: true }
|
||||
{ kind: 'map', label: '맵 약관', showInInstaller: true, showInInstallerRp: false, showInFinalResourcepack: false },
|
||||
{ kind: 'mod', label: '모드 약관', showInInstaller: true, showInInstallerRp: false, showInFinalResourcepack: false },
|
||||
{ kind: 'installer', label: '설치기 약관', showInInstaller: true, showInInstallerRp: false, showInFinalResourcepack: false },
|
||||
{ kind: 'resourcepack', label: '리소스팩 약관', showInInstaller: false, showInInstallerRp: true, showInFinalResourcepack: true },
|
||||
{ kind: 'installer-rp', label: '리소스팩 설치기 약관', showInInstaller: false, showInInstallerRp: true, showInFinalResourcepack: false }
|
||||
]
|
||||
|
||||
const TERM_KIND_RE = /^[a-z0-9][a-z0-9-]{0,31}$/
|
||||
@@ -411,6 +421,7 @@ export interface TermEntry {
|
||||
label: string
|
||||
showInInstaller: boolean
|
||||
showInInstallerRp: boolean
|
||||
showInFinalResourcepack: boolean
|
||||
}
|
||||
|
||||
interface TermsMeta {
|
||||
@@ -502,7 +513,8 @@ async function ensureMetaInitialized(dir: string, dirWasJustCreated: boolean): P
|
||||
meta.terms[seed.kind] = {
|
||||
label: seed.label,
|
||||
showInInstaller: seed.showInInstaller,
|
||||
showInInstallerRp: seed.showInInstallerRp
|
||||
showInInstallerRp: seed.showInInstallerRp,
|
||||
showInFinalResourcepack: seed.showInFinalResourcepack
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
@@ -543,10 +555,11 @@ async function ensureMetaInitialized(dir: string, dirWasJustCreated: boolean): P
|
||||
terms[seed.kind] = {
|
||||
label: seed.label,
|
||||
showInInstaller: seed.showInInstaller,
|
||||
showInInstallerRp: seed.showInInstallerRp
|
||||
showInInstallerRp: seed.showInInstallerRp,
|
||||
showInFinalResourcepack: seed.showInFinalResourcepack
|
||||
}
|
||||
}
|
||||
// 구 스키마의 사용자 정의 약관은 양쪽 인스톨러에 보이도록 기본값으로.
|
||||
// 구 스키마의 사용자 정의 약관은 양쪽 인스톨러에 보이도록 기본값으로(최종 리소스팩은 기본 off).
|
||||
for (const [k, label] of Object.entries(oldCustomLabels)) {
|
||||
if (terms[k]) continue
|
||||
try {
|
||||
@@ -554,7 +567,7 @@ async function ensureMetaInitialized(dir: string, dirWasJustCreated: boolean): P
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
terms[k] = { label, showInInstaller: true, showInInstallerRp: true }
|
||||
terms[k] = { label, showInInstaller: true, showInInstallerRp: true, showInFinalResourcepack: false }
|
||||
}
|
||||
await fsp.writeFile(metaPath, `${JSON.stringify({ terms }, null, 2)}\n`, 'utf8')
|
||||
}
|
||||
@@ -575,7 +588,8 @@ async function loadTermsMeta(packKey: string): Promise<TermsMeta> {
|
||||
result.terms[k] = {
|
||||
label,
|
||||
showInInstaller: entry.showInInstaller === true,
|
||||
showInInstallerRp: entry.showInInstallerRp === true
|
||||
showInInstallerRp: entry.showInInstallerRp === true,
|
||||
showInFinalResourcepack: entry.showInFinalResourcepack === true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -600,6 +614,7 @@ export interface TermItem {
|
||||
label: string
|
||||
showInInstaller: boolean
|
||||
showInInstallerRp: boolean
|
||||
showInFinalResourcepack: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -633,7 +648,8 @@ export async function listTermsWithLabels(packKey: string): Promise<TermItem[]>
|
||||
kind: seed.kind,
|
||||
label: entry.label,
|
||||
showInInstaller: entry.showInInstaller,
|
||||
showInInstallerRp: entry.showInInstallerRp
|
||||
showInInstallerRp: entry.showInInstallerRp,
|
||||
showInFinalResourcepack: entry.showInFinalResourcepack
|
||||
})
|
||||
seen.add(seed.kind)
|
||||
}
|
||||
@@ -647,7 +663,8 @@ export async function listTermsWithLabels(packKey: string): Promise<TermItem[]>
|
||||
kind,
|
||||
label: entry.label,
|
||||
showInInstaller: entry.showInInstaller,
|
||||
showInInstallerRp: entry.showInInstallerRp
|
||||
showInInstallerRp: entry.showInInstallerRp,
|
||||
showInFinalResourcepack: entry.showInFinalResourcepack
|
||||
})
|
||||
}
|
||||
return items
|
||||
@@ -666,7 +683,7 @@ export async function getTermEntry(packKey: string, kind: string): Promise<TermE
|
||||
export async function setTermVisibility(
|
||||
packKey: string,
|
||||
kind: string,
|
||||
visibility: { showInInstaller: boolean; showInInstallerRp: boolean }
|
||||
visibility: { showInInstaller: boolean; showInInstallerRp: boolean; showInFinalResourcepack: boolean }
|
||||
): Promise<void> {
|
||||
if (!isTermKind(kind)) throw new Error('invalid term kind')
|
||||
const meta = await loadTermsMeta(packKey)
|
||||
@@ -674,6 +691,7 @@ export async function setTermVisibility(
|
||||
if (!entry) throw new Error('term not found')
|
||||
entry.showInInstaller = !!visibility.showInInstaller
|
||||
entry.showInInstallerRp = !!visibility.showInInstallerRp
|
||||
entry.showInFinalResourcepack = !!visibility.showInFinalResourcepack
|
||||
await saveTermsMeta(packKey, meta)
|
||||
}
|
||||
|
||||
@@ -723,7 +741,8 @@ export async function createTerm(packKey: string, kind: string, label: string):
|
||||
meta.terms[kind] = {
|
||||
label: cleanLabel,
|
||||
showInInstaller: seed ? seed.showInInstaller : true,
|
||||
showInInstallerRp: seed ? seed.showInInstallerRp : true
|
||||
showInInstallerRp: seed ? seed.showInInstallerRp : true,
|
||||
showInFinalResourcepack: seed ? seed.showInFinalResourcepack : false
|
||||
}
|
||||
await saveTermsMeta(packKey, meta)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user