Switch mods to per-folder auto-discovery and resourcepack to single zip

- PackDefinition: replace mods[]/resourcepacks[] with modsFolder (string) + resourcepackPath (string); drop PackAsset
- Editor: replace dynamic add/remove lists with two single inputs; remove the now-dead JS for adding/removing rows
- Server: expose GET /file/mods/<folder>/index.json that returns the list of .jar names; folder name restricted to [a-zA-Z0-9_-]+
- Installer: fetch the listing JSON and download each jar from /file/mods/<folder>/<file>.jar; download the single resourcepack from /file/resourcepacks/<file>.zip directly into resourcepacks/

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 20:51:44 +09:00
parent 9c4f0e8dbc
commit 44847b8a55
9 changed files with 99 additions and 122 deletions

View File

@@ -38,7 +38,7 @@
<ul class="metaList">
<li>MC <%= item.definition.mcVersion %></li>
<li>플랫폼 <%= item.definition.platform.type %></li>
<li>모드 <%= item.definition.mods.length %></li>
<li>모드 폴더 <%= item.definition.modsFolder || '없음' %></li>
</ul>
<% } %>
</a>

View File

@@ -80,33 +80,18 @@
</label>
</div>
<fieldset class="dynamicListFieldset">
<legend>모드 (.jar)</legend>
<div class="dynamicList" data-list="mods">
<% pack.mods.forEach(function (mod) { %>
<div class="dynamicRow">
<input name="modName" placeholder="모드 이름" value="<%= mod.name %>" />
<input name="modUrl" placeholder="다운로드 URL" value="<%= mod.downloadUrl %>" />
<button type="button" class="dangerLink dynamicRemove">삭제</button>
</div>
<% }) %>
</div>
<button type="button" class="secondaryButton" data-add="mods">모드 추가</button>
</fieldset>
<fieldset class="dynamicListFieldset">
<legend>리소스팩 (.zip)</legend>
<div class="dynamicList" data-list="resourcepacks">
<% pack.resourcepacks.forEach(function (resourcePack) { %>
<div class="dynamicRow">
<input name="resourceName" placeholder="리소스팩 이름" value="<%= resourcePack.name %>" />
<input name="resourceUrl" placeholder="다운로드 URL" value="<%= resourcePack.downloadUrl %>" />
<button type="button" class="dangerLink dynamicRemove">삭제</button>
</div>
<% }) %>
</div>
<button type="button" class="secondaryButton" data-add="resourcepacks">리소스팩 추가</button>
</fieldset>
<div class="gridTwo">
<label>
<span>모드 폴더 이름</span>
<input name="modsFolder" value="<%= pack.modsFolder %>" placeholder="my-pack" pattern="[a-zA-Z0-9_\-]*" />
<small class="muted">/file/mods/&lt;폴더이름&gt;/ 안의 모든 .jar을 자동으로 받습니다. 비워두면 모드를 받지 않습니다.</small>
</label>
<label>
<span>리소스팩 (.zip)</span>
<input name="resourcepackPath" value="<%= pack.resourcepackPath %>" placeholder="my-pack.zip" pattern=".*\.zip|" />
<small class="muted">/file/resourcepacks/ 아래 .zip 파일 이름. 비워두면 리소스팩을 받지 않습니다.</small>
</label>
</div>
<button class="primaryButton" type="submit">저장</button>
</form>
@@ -129,32 +114,6 @@
platformSelect.addEventListener('change', syncPlatformVisibility)
syncPlatformVisibility()
document.querySelectorAll('[data-add]').forEach(function (button) {
button.addEventListener('click', function () {
var listKey = button.getAttribute('data-add')
var list = document.querySelector('[data-list="' + listKey + '"]')
if (!list) return
var nameField = listKey === 'mods' ? 'modName' : 'resourceName'
var urlField = listKey === 'mods' ? 'modUrl' : 'resourceUrl'
var placeholder = listKey === 'mods' ? '모드 이름' : '리소스팩 이름'
var row = document.createElement('div')
row.className = 'dynamicRow'
row.innerHTML =
'<input name="' + nameField + '" placeholder="' + placeholder + '" />' +
'<input name="' + urlField + '" placeholder="다운로드 URL" />' +
'<button type="button" class="dangerLink dynamicRemove">삭제</button>'
list.appendChild(row)
})
})
document.addEventListener('click', function (event) {
var target = event.target
if (!(target instanceof HTMLElement)) return
if (!target.classList.contains('dynamicRemove')) return
var row = target.closest('.dynamicRow')
if (row) row.remove()
})
var form = document.getElementById('editorForm')
form.addEventListener('submit', function (event) {
var clientMin = Number(form.clientMinRam.value)